feat(vibe-stories): add new vibe stories section with interactive carousel
This commit is contained in:
@@ -22,3 +22,4 @@ docs/.vitepress/.temp/*
|
|||||||
.vitepress/cache/*
|
.vitepress/cache/*
|
||||||
docs/archived-components.md
|
docs/archived-components.md
|
||||||
.claude/skills/*
|
.claude/skills/*
|
||||||
|
temp/*
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
@@ -1414,9 +1414,34 @@ Sitemap: ${siteUrl}/sitemap.xml
|
|||||||
text: '附录知识库',
|
text: '附录知识库',
|
||||||
link: '/zh-cn/appendix/index',
|
link: '/zh-cn/appendix/index',
|
||||||
activeMatch: '/zh-cn/appendix/'
|
activeMatch: '/zh-cn/appendix/'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Vibe 故事',
|
||||||
|
link: '/zh-cn/vibe-stories/story-1',
|
||||||
|
activeMatch: '/zh-cn/vibe-stories/'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
sidebar: {
|
sidebar: {
|
||||||
|
'/zh-cn/vibe-stories/': [
|
||||||
|
{
|
||||||
|
text: 'Vibe 故事',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: '我的第一个全栈项目',
|
||||||
|
link: '/zh-cn/vibe-stories/story-1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '从产品经理到独立开发者',
|
||||||
|
link: '/zh-cn/vibe-stories/story-2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '用 AI 提效 10 倍',
|
||||||
|
link: '/zh-cn/vibe-stories/story-3'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
'/zh-cn/stage-0/': productManagerSidebar,
|
'/zh-cn/stage-0/': productManagerSidebar,
|
||||||
'/zh-cn/stage-1/': productManagerSidebar,
|
'/zh-cn/stage-1/': productManagerSidebar,
|
||||||
'/zh-cn/stage-2/': [
|
'/zh-cn/stage-2/': [
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||||
import { useRouter, withBase, useData } from 'vitepress'
|
import { useRouter, withBase, useData } from 'vitepress'
|
||||||
import GitHubStars from './GitHubStars.vue'
|
import GitHubStars from './GitHubStars.vue'
|
||||||
|
import VibeStories from './VibeStories.vue'
|
||||||
|
import { provide } from 'vue'
|
||||||
import stage2LovartCover from '../../../zh-cn/stage-2/frontend/2.0-lovart-assets/images/image1.png'
|
import stage2LovartCover from '../../../zh-cn/stage-2/frontend/2.0-lovart-assets/images/image1.png'
|
||||||
import stage2FigmaCover from '../../../zh-cn/stage-2/frontend/2.1-figma-mastergo/images/image8.png'
|
import stage2FigmaCover from '../../../zh-cn/stage-2/frontend/2.1-figma-mastergo/images/image8.png'
|
||||||
import stage2DesignToCodeCover from '../../../zh-cn/stage-2/frontend/2.6-design-to-code/images/image42.png'
|
import stage2DesignToCodeCover from '../../../zh-cn/stage-2/frontend/2.6-design-to-code/images/image42.png'
|
||||||
@@ -18,6 +20,7 @@ const { site, page, lang } = useData()
|
|||||||
const activeTab = ref('home')
|
const activeTab = ref('home')
|
||||||
const showLangMenu = ref(false)
|
const showLangMenu = ref(false)
|
||||||
const topPromoProgress = ref(1)
|
const topPromoProgress = ref(1)
|
||||||
|
const topPromoDismissed = ref(false)
|
||||||
const topPromoIntroProgress = ref(0)
|
const topPromoIntroProgress = ref(0)
|
||||||
const topPromoColorProgress = ref(0)
|
const topPromoColorProgress = ref(0)
|
||||||
let topPromoIntroRaf = 0
|
let topPromoIntroRaf = 0
|
||||||
@@ -27,7 +30,7 @@ const WELCOME_SEEN_KEY = 'easy-vibe-welcome-seen'
|
|||||||
|
|
||||||
// Appendix Scroll Logic
|
// Appendix Scroll Logic
|
||||||
const appendixWrapper = ref(null)
|
const appendixWrapper = ref(null)
|
||||||
const pmSection = ref(null)
|
const vibeStoriesSection = ref(null)
|
||||||
const totalPages = ref(1)
|
const totalPages = ref(1)
|
||||||
const currentPage = ref(0)
|
const currentPage = ref(0)
|
||||||
|
|
||||||
@@ -89,12 +92,21 @@ const i18n = {
|
|||||||
nav: {
|
nav: {
|
||||||
title: 'Easy-Vibe 教程',
|
title: 'Easy-Vibe 教程',
|
||||||
home: '首页',
|
home: '首页',
|
||||||
|
stories: 'Vibe 故事',
|
||||||
pm: '零基础入门',
|
pm: '零基础入门',
|
||||||
junior: '初中级开发',
|
junior: '初中级开发',
|
||||||
senior: '高级开发',
|
senior: '高级开发',
|
||||||
appendix: '附录',
|
appendix: '附录',
|
||||||
start: '开始学习'
|
start: '开始学习'
|
||||||
},
|
},
|
||||||
|
stories: {
|
||||||
|
cat: 'Vibe Stories',
|
||||||
|
title: '看见每一个<br><span class="highlight">闪光的你。</span>',
|
||||||
|
sub: '在这里,发现大家如何使用 AI 创造属于自己的作品。',
|
||||||
|
s1: { title: '我的第一个全栈项目', author: 'Sanbu' },
|
||||||
|
s2: { title: '从产品经理到独立开发者', author: 'Alice' },
|
||||||
|
s3: { title: '用 AI 提效 10 倍', author: 'Bob' }
|
||||||
|
},
|
||||||
stage1: {
|
stage1: {
|
||||||
cat: 'Stage 1 · 零基础入门',
|
cat: 'Stage 1 · 零基础入门',
|
||||||
title:
|
title:
|
||||||
@@ -268,12 +280,21 @@ const i18n = {
|
|||||||
nav: {
|
nav: {
|
||||||
title: 'Easy-Vibe Tutorial',
|
title: 'Easy-Vibe Tutorial',
|
||||||
home: 'Home',
|
home: 'Home',
|
||||||
|
stories: 'Vibe Stories',
|
||||||
pm: 'Product Manager',
|
pm: 'Product Manager',
|
||||||
junior: 'Junior Dev',
|
junior: 'Junior Dev',
|
||||||
senior: 'Senior Dev',
|
senior: 'Senior Dev',
|
||||||
appendix: 'Appendix',
|
appendix: 'Appendix',
|
||||||
start: 'Start Learning'
|
start: 'Start Learning'
|
||||||
},
|
},
|
||||||
|
stories: {
|
||||||
|
cat: 'Vibe Stories',
|
||||||
|
title: 'See every <br><span class="highlight">shining you.</span>',
|
||||||
|
sub: 'Discover how everyone uses AI to create their own projects here.',
|
||||||
|
s1: { title: 'My First Full-Stack Project', author: 'Sanbu' },
|
||||||
|
s2: { title: 'From PM to Indie Hacker', author: 'Alice' },
|
||||||
|
s3: { title: '10x Productivity with AI', author: 'Bob' }
|
||||||
|
},
|
||||||
stage1: {
|
stage1: {
|
||||||
cat: 'Stage 1 · Getting Started',
|
cat: 'Stage 1 · Getting Started',
|
||||||
title: 'Zero to Hero, <br><span class="highlight">Be Your Own PM.</span>',
|
title: 'Zero to Hero, <br><span class="highlight">Be Your Own PM.</span>',
|
||||||
@@ -449,12 +470,21 @@ const i18n = {
|
|||||||
nav: {
|
nav: {
|
||||||
title: 'Easy-Vibe チュートリアル',
|
title: 'Easy-Vibe チュートリアル',
|
||||||
home: 'ホーム',
|
home: 'ホーム',
|
||||||
|
stories: 'Vibe Stories',
|
||||||
pm: 'プロダクトマネージャー',
|
pm: 'プロダクトマネージャー',
|
||||||
junior: '初中級開発者',
|
junior: '初中級開発者',
|
||||||
senior: '上級開発者',
|
senior: '上級開発者',
|
||||||
appendix: '付録',
|
appendix: '付録',
|
||||||
start: '学習を開始'
|
start: '学習を開始'
|
||||||
},
|
},
|
||||||
|
stories: {
|
||||||
|
cat: 'Vibe Stories',
|
||||||
|
title: '輝くあなたを<br><span class="highlight">見つけよう。</span>',
|
||||||
|
sub: 'みんながAIを使ってどのように自分の作品を作っているか発見しましょう。',
|
||||||
|
s1: { title: '初めてのフルスタックプロジェクト', author: 'Sanbu' },
|
||||||
|
s2: { title: 'PMから独立系開発者へ', author: 'Alice' },
|
||||||
|
s3: { title: 'AIで生産性を10倍に', author: 'Bob' }
|
||||||
|
},
|
||||||
stage1: {
|
stage1: {
|
||||||
cat: 'Stage 1 · 初心者とPM',
|
cat: 'Stage 1 · 初心者とPM',
|
||||||
title:
|
title:
|
||||||
@@ -572,12 +602,18 @@ const i18n = {
|
|||||||
nav: {
|
nav: {
|
||||||
title: 'Easy-Vibe 教學',
|
title: 'Easy-Vibe 教學',
|
||||||
home: '首頁',
|
home: '首頁',
|
||||||
|
stories: 'Vibe 故事',
|
||||||
pm: '產品經理',
|
pm: '產品經理',
|
||||||
junior: '初中級開發',
|
junior: '初中級開發',
|
||||||
senior: '高級開發',
|
senior: '高級開發',
|
||||||
appendix: '附錄',
|
appendix: '附錄',
|
||||||
start: '開始學習'
|
start: '開始學習'
|
||||||
},
|
},
|
||||||
|
stories: {
|
||||||
|
cat: 'Vibe Stories',
|
||||||
|
title: '看見每一個<br><span class="highlight">閃光的你。</span>',
|
||||||
|
sub: '在這裡,發現大家如何使用 AI 創造屬於自己的作品。'
|
||||||
|
},
|
||||||
stage1: {
|
stage1: {
|
||||||
cat: 'Stage 1 · 新手與產品原型',
|
cat: 'Stage 1 · 新手與產品原型',
|
||||||
title:
|
title:
|
||||||
@@ -693,12 +729,18 @@ const i18n = {
|
|||||||
nav: {
|
nav: {
|
||||||
title: 'Easy-Vibe 튜토리얼',
|
title: 'Easy-Vibe 튜토리얼',
|
||||||
home: '홈',
|
home: '홈',
|
||||||
|
stories: 'Vibe Stories',
|
||||||
pm: '제품 관리자',
|
pm: '제품 관리자',
|
||||||
junior: '초/중급 개발자',
|
junior: '초/중급 개발자',
|
||||||
senior: '고급 개발자',
|
senior: '고급 개발자',
|
||||||
appendix: '부록',
|
appendix: '부록',
|
||||||
start: '학습 시작'
|
start: '학습 시작'
|
||||||
},
|
},
|
||||||
|
stories: {
|
||||||
|
cat: 'Vibe Stories',
|
||||||
|
title: '빛나는 당신을 <br><span class="highlight">발견하세요.</span>',
|
||||||
|
sub: '모두가 AI를 사용하여 어떻게 자신만의 작품을 만드는지 여기서 확인하세요.'
|
||||||
|
},
|
||||||
stage1: {
|
stage1: {
|
||||||
cat: 'Stage 1 · 초보자 & PM',
|
cat: 'Stage 1 · 초보자 & PM',
|
||||||
title:
|
title:
|
||||||
@@ -816,12 +858,18 @@ const i18n = {
|
|||||||
nav: {
|
nav: {
|
||||||
title: 'Tutorial Easy-Vibe',
|
title: 'Tutorial Easy-Vibe',
|
||||||
home: 'Inicio',
|
home: 'Inicio',
|
||||||
|
stories: 'Vibe Stories',
|
||||||
pm: 'Gerente de Producto',
|
pm: 'Gerente de Producto',
|
||||||
junior: 'Desarrollador Junior',
|
junior: 'Desarrollador Junior',
|
||||||
senior: 'Desarrollador Senior',
|
senior: 'Desarrollador Senior',
|
||||||
appendix: 'Apéndice',
|
appendix: 'Apéndice',
|
||||||
start: 'Empezar'
|
start: 'Empezar'
|
||||||
},
|
},
|
||||||
|
stories: {
|
||||||
|
cat: 'Vibe Stories',
|
||||||
|
title: 'Mira a cada <br><span class="highlight">tú brillante.</span>',
|
||||||
|
sub: 'Descubre cómo todos usan la IA para crear sus propios proyectos aquí.'
|
||||||
|
},
|
||||||
stage1: {
|
stage1: {
|
||||||
cat: 'Stage 1 · Principiante y PM',
|
cat: 'Stage 1 · Principiante y PM',
|
||||||
title:
|
title:
|
||||||
@@ -939,12 +987,18 @@ const i18n = {
|
|||||||
nav: {
|
nav: {
|
||||||
title: 'Tutoriel Easy-Vibe',
|
title: 'Tutoriel Easy-Vibe',
|
||||||
home: 'Accueil',
|
home: 'Accueil',
|
||||||
|
stories: 'Vibe Stories',
|
||||||
pm: 'Chef de Produit',
|
pm: 'Chef de Produit',
|
||||||
junior: 'Dév Junior',
|
junior: 'Dév Junior',
|
||||||
senior: 'Dév Senior',
|
senior: 'Dév Senior',
|
||||||
appendix: 'Annexe',
|
appendix: 'Annexe',
|
||||||
start: 'Commencer'
|
start: 'Commencer'
|
||||||
},
|
},
|
||||||
|
stories: {
|
||||||
|
cat: 'Vibe Stories',
|
||||||
|
title: 'Voyez chaque <br><span class="highlight">vous brillant.</span>',
|
||||||
|
sub: 'Découvrez comment chacun utilise l\'IA pour créer ses propres projets ici.'
|
||||||
|
},
|
||||||
stage1: {
|
stage1: {
|
||||||
cat: 'Stage 1 · Débutant & PM',
|
cat: 'Stage 1 · Débutant & PM',
|
||||||
title:
|
title:
|
||||||
@@ -1063,12 +1117,18 @@ const i18n = {
|
|||||||
nav: {
|
nav: {
|
||||||
title: 'Easy-Vibe Tutorial',
|
title: 'Easy-Vibe Tutorial',
|
||||||
home: 'Startseite',
|
home: 'Startseite',
|
||||||
|
stories: 'Vibe Stories',
|
||||||
pm: 'Produktmanager',
|
pm: 'Produktmanager',
|
||||||
junior: 'Junior Dev',
|
junior: 'Junior Dev',
|
||||||
senior: 'Senior Dev',
|
senior: 'Senior Dev',
|
||||||
appendix: 'Anhang',
|
appendix: 'Anhang',
|
||||||
start: 'Starten'
|
start: 'Starten'
|
||||||
},
|
},
|
||||||
|
stories: {
|
||||||
|
cat: 'Vibe Stories',
|
||||||
|
title: 'Sehen Sie jeden <br><span class="highlight">strahlenden Sie.</span>',
|
||||||
|
sub: 'Entdecken Sie hier, wie jeder KI nutzt, um eigene Projekte zu erstellen.'
|
||||||
|
},
|
||||||
stage1: {
|
stage1: {
|
||||||
cat: 'Stage 1 · Anfänger & PM',
|
cat: 'Stage 1 · Anfänger & PM',
|
||||||
title:
|
title:
|
||||||
@@ -1184,14 +1244,20 @@ const i18n = {
|
|||||||
},
|
},
|
||||||
'ar-sa': {
|
'ar-sa': {
|
||||||
nav: {
|
nav: {
|
||||||
title: 'Easy-Vibe درس تعليمي',
|
title: 'دليل Easy-Vibe',
|
||||||
home: 'الرئيسية',
|
home: 'الرئيسية',
|
||||||
|
stories: 'Vibe Stories',
|
||||||
pm: 'مدير المنتج',
|
pm: 'مدير المنتج',
|
||||||
junior: 'مطور مبتدئ',
|
junior: 'مطور مبتدئ',
|
||||||
senior: 'مطور خبير',
|
senior: 'مطور خبير',
|
||||||
appendix: 'ملحق',
|
appendix: 'ملحق',
|
||||||
start: 'ابدأ التعلم'
|
start: 'ابدأ التعلم'
|
||||||
},
|
},
|
||||||
|
stories: {
|
||||||
|
cat: 'Vibe Stories',
|
||||||
|
title: 'شاهد كل <br><span class="highlight">أنت متألق.</span>',
|
||||||
|
sub: 'اكتشف كيف يستخدم الجميع الذكاء الاصطناعي لإنشاء مشاريعهم الخاصة هنا.'
|
||||||
|
},
|
||||||
stage1: {
|
stage1: {
|
||||||
cat: 'Stage 1 · مدير المنتج',
|
cat: 'Stage 1 · مدير المنتج',
|
||||||
title:
|
title:
|
||||||
@@ -1310,12 +1376,18 @@ const i18n = {
|
|||||||
nav: {
|
nav: {
|
||||||
title: 'Hướng dẫn Easy-Vibe',
|
title: 'Hướng dẫn Easy-Vibe',
|
||||||
home: 'Trang chủ',
|
home: 'Trang chủ',
|
||||||
|
stories: 'Vibe Stories',
|
||||||
pm: 'Quản lý sản phẩm',
|
pm: 'Quản lý sản phẩm',
|
||||||
junior: 'Dev Sơ/Trung cấp',
|
junior: 'Dev Sơ/Trung cấp',
|
||||||
senior: 'Dev Cao cấp',
|
senior: 'Dev Cao cấp',
|
||||||
appendix: 'Phụ lục',
|
appendix: 'Phụ lục',
|
||||||
start: 'Bắt đầu học'
|
start: 'Bắt đầu học'
|
||||||
},
|
},
|
||||||
|
stories: {
|
||||||
|
cat: 'Vibe Stories',
|
||||||
|
title: 'Thấy mọi <br><span class="highlight">bạn tỏa sáng.</span>',
|
||||||
|
sub: 'Khám phá cách mọi người sử dụng AI để tạo các dự án riêng của họ tại đây.'
|
||||||
|
},
|
||||||
stage1: {
|
stage1: {
|
||||||
cat: 'Stage 1 · Người mới & PM',
|
cat: 'Stage 1 · Người mới & PM',
|
||||||
title:
|
title:
|
||||||
@@ -1437,6 +1509,8 @@ const t = computed(() => {
|
|||||||
return i18n[code] || i18n['en']
|
return i18n[code] || i18n['en']
|
||||||
})
|
})
|
||||||
|
|
||||||
|
provide('t', t)
|
||||||
|
|
||||||
const isCjkLocale = computed(() => {
|
const isCjkLocale = computed(() => {
|
||||||
const code = lang.value ? lang.value.toLowerCase() : ''
|
const code = lang.value ? lang.value.toLowerCase() : ''
|
||||||
if (['zh-cn', 'zh-tw', 'ja-jp', 'ko-kr'].includes(code)) {
|
if (['zh-cn', 'zh-tw', 'ja-jp', 'ko-kr'].includes(code)) {
|
||||||
@@ -1610,6 +1684,27 @@ const toggleLangMenu = () => {
|
|||||||
showLangMenu.value = !showLangMenu.value
|
showLangMenu.value = !showLangMenu.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateHash = (id) => {
|
||||||
|
const targetHash = id === 'home' ? '#home' : `#${id}`
|
||||||
|
const currentUrl = `${window.location.pathname}${window.location.search}${window.location.hash}`
|
||||||
|
const nextUrl = `${window.location.pathname}${window.location.search}${targetHash}`
|
||||||
|
if (currentUrl !== nextUrl) {
|
||||||
|
window.history.replaceState(null, '', nextUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncTopPromoWithHash = () => {
|
||||||
|
const rawHash = window.location.hash.replace(/^#/, '')
|
||||||
|
const targetId = rawHash || 'home'
|
||||||
|
if (targetId === 'home') {
|
||||||
|
topPromoDismissed.value = false
|
||||||
|
topPromoProgress.value = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
topPromoDismissed.value = true
|
||||||
|
topPromoProgress.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
const changeLang = (targetLocale) => {
|
const changeLang = (targetLocale) => {
|
||||||
const currentPath = router.route.path
|
const currentPath = router.route.path
|
||||||
// Find current locale based on path prefix
|
// Find current locale based on path prefix
|
||||||
@@ -1628,7 +1723,8 @@ const changeLang = (targetLocale) => {
|
|||||||
newPath = `/${targetLocale}/`
|
newPath = `/${targetLocale}/`
|
||||||
}
|
}
|
||||||
|
|
||||||
router.go(withBase(newPath))
|
const hash = window.location.hash || ''
|
||||||
|
router.go(withBase(`${newPath}${hash}`))
|
||||||
showLangMenu.value = false
|
showLangMenu.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1636,17 +1732,43 @@ const scrollTo = (id) => {
|
|||||||
if (id === 'home') {
|
if (id === 'home') {
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
activeTab.value = 'home'
|
activeTab.value = 'home'
|
||||||
|
updateHash('home')
|
||||||
|
syncTopPromoWithHash()
|
||||||
|
updateTopPromoVisibility()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const el = document.getElementById(id)
|
const el = document.getElementById(id)
|
||||||
if (el) {
|
if (el) {
|
||||||
const navHeight = 48 // Approximate nav height
|
const navHeight = 48
|
||||||
// Use getBoundingClientRect for better accuracy
|
|
||||||
const elementPosition = el.getBoundingClientRect().top + window.pageYOffset
|
const elementPosition = el.getBoundingClientRect().top + window.pageYOffset
|
||||||
// Increase buffer to ensure section header is clearly visible below nav
|
const extraOffset = id === 'vibe-stories' ? 20 : 40
|
||||||
const offset = elementPosition - navHeight - 64
|
const offset = elementPosition - navHeight - extraOffset
|
||||||
window.scrollTo({ top: offset, behavior: 'smooth' })
|
window.scrollTo({ top: offset, behavior: 'smooth' })
|
||||||
activeTab.value = id
|
activeTab.value = id
|
||||||
|
updateHash(id)
|
||||||
|
syncTopPromoWithHash()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollToHashTarget = (behavior = 'auto') => {
|
||||||
|
const rawHash = window.location.hash.replace(/^#/, '')
|
||||||
|
const targetId = rawHash || 'home'
|
||||||
|
if (targetId === 'home') {
|
||||||
|
window.scrollTo({ top: 0, behavior })
|
||||||
|
activeTab.value = 'home'
|
||||||
|
syncTopPromoWithHash()
|
||||||
|
updateTopPromoVisibility()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const el = document.getElementById(targetId)
|
||||||
|
if (el) {
|
||||||
|
const navHeight = 48
|
||||||
|
const elementPosition = el.getBoundingClientRect().top + window.pageYOffset
|
||||||
|
const extraOffset = targetId === 'vibe-stories' ? 20 : 40
|
||||||
|
const offset = elementPosition - navHeight - extraOffset
|
||||||
|
window.scrollTo({ top: offset, behavior })
|
||||||
|
activeTab.value = targetId
|
||||||
|
syncTopPromoWithHash()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1658,12 +1780,17 @@ const closeLangMenu = (e) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateTopPromoVisibility = () => {
|
const updateTopPromoVisibility = () => {
|
||||||
if (!pmSection.value) {
|
if (topPromoDismissed.value) {
|
||||||
|
topPromoProgress.value = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!vibeStoriesSection.value) {
|
||||||
topPromoProgress.value = 1
|
topPromoProgress.value = 1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const navHeight = 44
|
const navHeight = 44
|
||||||
const sectionTop = pmSection.value.getBoundingClientRect().top + window.pageYOffset
|
const sectionTop =
|
||||||
|
vibeStoriesSection.value.getBoundingClientRect().top + window.pageYOffset
|
||||||
const endY = sectionTop - navHeight
|
const endY = sectionTop - navHeight
|
||||||
const startY = endY - 96
|
const startY = endY - 96
|
||||||
const scrollY = window.pageYOffset
|
const scrollY = window.pageYOffset
|
||||||
@@ -1673,6 +1800,7 @@ const updateTopPromoVisibility = () => {
|
|||||||
}
|
}
|
||||||
if (scrollY >= endY) {
|
if (scrollY >= endY) {
|
||||||
topPromoProgress.value = 0
|
topPromoProgress.value = 0
|
||||||
|
topPromoDismissed.value = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
topPromoProgress.value = (endY - scrollY) / (endY - startY)
|
topPromoProgress.value = (endY - scrollY) / (endY - startY)
|
||||||
@@ -1765,15 +1893,21 @@ onMounted(() => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('click', closeLangMenu)
|
document.addEventListener('click', closeLangMenu)
|
||||||
if (appendixWrapper.value) {
|
if (appendixWrapper.value) {
|
||||||
appendixWrapper.value.addEventListener('scroll', onAppendixScroll)
|
appendixWrapper.value.addEventListener('scroll', onAppendixScroll)
|
||||||
updatePagination()
|
updatePagination()
|
||||||
window.addEventListener('resize', updatePagination)
|
window.addEventListener('resize', updatePagination)
|
||||||
}
|
}
|
||||||
|
syncTopPromoWithHash()
|
||||||
|
window.setTimeout(() => {
|
||||||
|
scrollToHashTarget('auto')
|
||||||
|
}, 0)
|
||||||
updateTopPromoVisibility()
|
updateTopPromoVisibility()
|
||||||
window.addEventListener('scroll', updateTopPromoVisibility, { passive: true })
|
window.addEventListener('scroll', updateTopPromoVisibility, { passive: true })
|
||||||
window.addEventListener('resize', updateTopPromoVisibility)
|
window.addEventListener('resize', updateTopPromoVisibility)
|
||||||
|
window.addEventListener('hashchange', scrollToHashTarget)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -1796,6 +1930,7 @@ onUnmounted(() => {
|
|||||||
window.removeEventListener('resize', updatePagination)
|
window.removeEventListener('resize', updatePagination)
|
||||||
window.removeEventListener('scroll', updateTopPromoVisibility)
|
window.removeEventListener('scroll', updateTopPromoVisibility)
|
||||||
window.removeEventListener('resize', updateTopPromoVisibility)
|
window.removeEventListener('resize', updateTopPromoVisibility)
|
||||||
|
window.removeEventListener('hashchange', scrollToHashTarget)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Stage 1: 产品经理 (Web 原型)
|
// Stage 1: 产品经理 (Web 原型)
|
||||||
@@ -2021,6 +2156,13 @@ const appendixCards = [
|
|||||||
>
|
>
|
||||||
{{ t.nav.home }}
|
{{ t.nav.home }}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
:class="{ active: activeTab === 'vibe-stories' }"
|
||||||
|
class="nav-link-item"
|
||||||
|
@click="scrollTo('vibe-stories')"
|
||||||
|
>
|
||||||
|
{{ t.nav.stories || 'Vibe 故事' }}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
:class="{ active: activeTab === 'pm' }"
|
:class="{ active: activeTab === 'pm' }"
|
||||||
class="nav-link-item"
|
class="nav-link-item"
|
||||||
@@ -2104,13 +2246,18 @@ const appendixCards = [
|
|||||||
style="height: 0"
|
style="height: 0"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Vibe Stories -->
|
||||||
|
<section
|
||||||
|
id="vibe-stories"
|
||||||
|
ref="vibeStoriesSection"
|
||||||
|
class="section-container"
|
||||||
|
>
|
||||||
|
<VibeStories />
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="section-band section-band-learning">
|
<div class="section-band section-band-learning">
|
||||||
<!-- Stage 1: Product Manager -->
|
<!-- Stage 1: Product Manager -->
|
||||||
<section
|
<section id="pm" class="section-container section-pm">
|
||||||
id="pm"
|
|
||||||
ref="pmSection"
|
|
||||||
class="section-container section-pm"
|
|
||||||
>
|
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2 class="section-category">
|
<h2 class="section-category">
|
||||||
{{ t.stage1.cat }}
|
{{ t.stage1.cat }}
|
||||||
|
|||||||
@@ -0,0 +1,489 @@
|
|||||||
|
<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>
|
||||||
@@ -1779,7 +1779,10 @@ export default {
|
|||||||
container.classList.remove('mermaid-diagram-error')
|
container.classList.remove('mermaid-diagram-error')
|
||||||
container.setAttribute('role', 'button')
|
container.setAttribute('role', 'button')
|
||||||
container.setAttribute('tabindex', '0')
|
container.setAttribute('tabindex', '0')
|
||||||
container.setAttribute('aria-label', 'Open Mermaid diagram in fullscreen viewer')
|
container.setAttribute(
|
||||||
|
'aria-label',
|
||||||
|
'Open Mermaid diagram in fullscreen viewer'
|
||||||
|
)
|
||||||
container.onclick = (event) => {
|
container.onclick = (event) => {
|
||||||
if (event.target.closest?.('a')) return
|
if (event.target.closest?.('a')) return
|
||||||
openMermaidViewer(container)
|
openMermaidViewer(container)
|
||||||
|
|||||||
+23
-2
@@ -1147,7 +1147,7 @@
|
|||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/agent-teams/</loc>
|
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/agent-teams/</loc>
|
||||||
<lastmod>2026-03-25T00:28:36+08:00</lastmod>
|
<lastmod>2026-03-27T18:17:31+08:00</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/agent-teams/"/>
|
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/agent-teams/"/>
|
||||||
@@ -1171,7 +1171,7 @@
|
|||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/long-running-tasks/</loc>
|
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/long-running-tasks/</loc>
|
||||||
<lastmod>2026-03-25T00:28:36+08:00</lastmod>
|
<lastmod>2026-03-27T18:17:31+08:00</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/long-running-tasks/"/>
|
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/core-skills/long-running-tasks/"/>
|
||||||
@@ -1337,4 +1337,25 @@
|
|||||||
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/"/>
|
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/stage-3/personal-brand/3.7-personal-website-blog/"/>
|
||||||
<xhtml:link rel="alternate" hreflang="en" href="https://datawhalechina.github.io/easy-vibe/en/stage-3/personal-brand/3.7-personal-website-blog/"/>
|
<xhtml:link rel="alternate" hreflang="en" href="https://datawhalechina.github.io/easy-vibe/en/stage-3/personal-brand/3.7-personal-website-blog/"/>
|
||||||
</url>
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/vibe-stories/story-1/</loc>
|
||||||
|
<lastmod>2026-03-28T13:05:08.945Z</lastmod>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>0.6</priority>
|
||||||
|
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/vibe-stories/story-1/"/>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/vibe-stories/story-2/</loc>
|
||||||
|
<lastmod>2026-03-28T12:27:36.079Z</lastmod>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>0.6</priority>
|
||||||
|
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/vibe-stories/story-2/"/>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://datawhalechina.github.io/easy-vibe/zh-cn/vibe-stories/story-3/</loc>
|
||||||
|
<lastmod>2026-03-28T13:05:08.945Z</lastmod>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>0.6</priority>
|
||||||
|
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://datawhalechina.github.io/easy-vibe/zh-cn/vibe-stories/story-3/"/>
|
||||||
|
</url>
|
||||||
</urlset>
|
</urlset>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: 我的第一个全栈项目
|
||||||
|
description: 记录如何使用 AI 从零开始完成一个全栈项目。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 我的第一个全栈项目
|
||||||
|
|
||||||
|
> **作者:Sanbu**
|
||||||
|
> **头像:👨💻**
|
||||||
|
|
||||||
|
在这里,我将分享我是如何使用 AI(Cursor 和 Claude)在短短几天内完成我的第一个全栈项目的。
|
||||||
|
|
||||||
|
## 想法诞生
|
||||||
|
|
||||||
|
我一直想做一个属于自己的工具,但是因为缺乏技术背景,很多想法都停留在脑海里。直到我遇到了 Vibe Coding 的概念。
|
||||||
|
|
||||||
|
## 开发过程
|
||||||
|
|
||||||
|
1. **前端搭建**:通过对话,AI 帮我生成了 React 组件。
|
||||||
|
2. **后端对接**:使用 Supabase 作为后端,AI 帮我写了所有的增删改查逻辑。
|
||||||
|
3. **部署上线**:使用 Vercel 一键部署。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
AI 编程真的改变了普通人创造产品的方式。只要你有想法,就能把它变成现实!
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: 从产品经理到独立开发者
|
||||||
|
description: 一个产品经理如何使用 AI 实现身份的转变。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 从产品经理到独立开发者
|
||||||
|
|
||||||
|
> **作者:Alice**
|
||||||
|
> **头像:👩🎨**
|
||||||
|
|
||||||
|
作为一名产品经理,我习惯了写 PRD 和画原型,但总是需要依赖开发团队才能把产品做出来。现在,一切都不一样了。
|
||||||
|
|
||||||
|
## 我的转型之路
|
||||||
|
|
||||||
|
我开始学习 Vibe Coding 工作流,把画原型的精力直接用来和 AI 结对编程。
|
||||||
|
|
||||||
|
- 第一周:学会了如何写出结构化的提示词。
|
||||||
|
- 第二周:完成了一个基础的记账工具。
|
||||||
|
- 第一个月:上线了我的第一个商业化产品。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 给其他 PM 的建议
|
||||||
|
|
||||||
|
不要害怕代码,把 AI 当作你的首席开发工程师,你只需要做好产品定义。
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
title: 用 AI 提效 10 倍
|
||||||
|
description: 探索日常开发中的 AI 提效实践。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 用 AI 提效 10 倍
|
||||||
|
|
||||||
|
> **作者:Bob**
|
||||||
|
> **头像:🚀**
|
||||||
|
|
||||||
|
我已经有几年的开发经验了,一开始我对 AI 写代码持怀疑态度,但尝试之后,真的被震撼到了。
|
||||||
|
|
||||||
|
## 提效点
|
||||||
|
|
||||||
|
1. **写测试用例**:以前最讨厌写单测,现在 AI 几秒钟就能生成覆盖率很高的测试。
|
||||||
|
2. **重构老代码**:把长达几百行的意大利面条代码扔给 AI,它能完美地拆分成设计模式良好的模块。
|
||||||
|
3. **学习新技术**:遇到没用过的库,直接问 AI 要最佳实践。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
拥抱 AI,不要被时代淘汰。
|
||||||
Reference in New Issue
Block a user