diff --git a/.gitignore b/.gitignore index 5fad2af..b1ea543 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ docs/.vitepress/.temp/* .vitepress/cache/* docs/archived-components.md .claude/skills/* +temp/* diff --git a/assets/macbook.png b/assets/macbook.png new file mode 100644 index 0000000..00d4289 Binary files /dev/null and b/assets/macbook.png differ diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index 20eec8f..6c5f975 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -1414,9 +1414,34 @@ Sitemap: ${siteUrl}/sitemap.xml text: '附录知识库', link: '/zh-cn/appendix/index', activeMatch: '/zh-cn/appendix/' + }, + { + text: 'Vibe 故事', + link: '/zh-cn/vibe-stories/story-1', + activeMatch: '/zh-cn/vibe-stories/' } ], 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-1/': productManagerSidebar, '/zh-cn/stage-2/': [ diff --git a/docs/.vitepress/theme/components/HomeFeatures.vue b/docs/.vitepress/theme/components/HomeFeatures.vue index 6f39760..ec2bc4a 100644 --- a/docs/.vitepress/theme/components/HomeFeatures.vue +++ b/docs/.vitepress/theme/components/HomeFeatures.vue @@ -2,6 +2,8 @@ import { ref, onMounted, onUnmounted, computed } from 'vue' import { useRouter, withBase, useData } from 'vitepress' 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 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' @@ -18,6 +20,7 @@ const { site, page, lang } = useData() const activeTab = ref('home') const showLangMenu = ref(false) const topPromoProgress = ref(1) +const topPromoDismissed = ref(false) const topPromoIntroProgress = ref(0) const topPromoColorProgress = ref(0) let topPromoIntroRaf = 0 @@ -27,7 +30,7 @@ const WELCOME_SEEN_KEY = 'easy-vibe-welcome-seen' // Appendix Scroll Logic const appendixWrapper = ref(null) -const pmSection = ref(null) +const vibeStoriesSection = ref(null) const totalPages = ref(1) const currentPage = ref(0) @@ -89,12 +92,21 @@ const i18n = { nav: { title: 'Easy-Vibe 教程', home: '首页', + stories: 'Vibe 故事', pm: '零基础入门', junior: '初中级开发', senior: '高级开发', appendix: '附录', start: '开始学习' }, + stories: { + cat: 'Vibe Stories', + title: '看见每一个
闪光的你。', + sub: '在这里,发现大家如何使用 AI 创造属于自己的作品。', + s1: { title: '我的第一个全栈项目', author: 'Sanbu' }, + s2: { title: '从产品经理到独立开发者', author: 'Alice' }, + s3: { title: '用 AI 提效 10 倍', author: 'Bob' } + }, stage1: { cat: 'Stage 1 · 零基础入门', title: @@ -268,12 +280,21 @@ const i18n = { nav: { title: 'Easy-Vibe Tutorial', home: 'Home', + stories: 'Vibe Stories', pm: 'Product Manager', junior: 'Junior Dev', senior: 'Senior Dev', appendix: 'Appendix', start: 'Start Learning' }, + stories: { + cat: 'Vibe Stories', + title: 'See every
shining you.', + 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: { cat: 'Stage 1 · Getting Started', title: 'Zero to Hero,
Be Your Own PM.', @@ -449,12 +470,21 @@ const i18n = { nav: { title: 'Easy-Vibe チュートリアル', home: 'ホーム', + stories: 'Vibe Stories', pm: 'プロダクトマネージャー', junior: '初中級開発者', senior: '上級開発者', appendix: '付録', start: '学習を開始' }, + stories: { + cat: 'Vibe Stories', + title: '輝くあなたを
見つけよう。', + sub: 'みんながAIを使ってどのように自分の作品を作っているか発見しましょう。', + s1: { title: '初めてのフルスタックプロジェクト', author: 'Sanbu' }, + s2: { title: 'PMから独立系開発者へ', author: 'Alice' }, + s3: { title: 'AIで生産性を10倍に', author: 'Bob' } + }, stage1: { cat: 'Stage 1 · 初心者とPM', title: @@ -572,12 +602,18 @@ const i18n = { nav: { title: 'Easy-Vibe 教學', home: '首頁', + stories: 'Vibe 故事', pm: '產品經理', junior: '初中級開發', senior: '高級開發', appendix: '附錄', start: '開始學習' }, + stories: { + cat: 'Vibe Stories', + title: '看見每一個
閃光的你。', + sub: '在這裡,發現大家如何使用 AI 創造屬於自己的作品。' + }, stage1: { cat: 'Stage 1 · 新手與產品原型', title: @@ -693,12 +729,18 @@ const i18n = { nav: { title: 'Easy-Vibe 튜토리얼', home: '홈', + stories: 'Vibe Stories', pm: '제품 관리자', junior: '초/중급 개발자', senior: '고급 개발자', appendix: '부록', start: '학습 시작' }, + stories: { + cat: 'Vibe Stories', + title: '빛나는 당신을
발견하세요.', + sub: '모두가 AI를 사용하여 어떻게 자신만의 작품을 만드는지 여기서 확인하세요.' + }, stage1: { cat: 'Stage 1 · 초보자 & PM', title: @@ -816,12 +858,18 @@ const i18n = { nav: { title: 'Tutorial Easy-Vibe', home: 'Inicio', + stories: 'Vibe Stories', pm: 'Gerente de Producto', junior: 'Desarrollador Junior', senior: 'Desarrollador Senior', appendix: 'Apéndice', start: 'Empezar' }, + stories: { + cat: 'Vibe Stories', + title: 'Mira a cada
tú brillante.', + sub: 'Descubre cómo todos usan la IA para crear sus propios proyectos aquí.' + }, stage1: { cat: 'Stage 1 · Principiante y PM', title: @@ -939,12 +987,18 @@ const i18n = { nav: { title: 'Tutoriel Easy-Vibe', home: 'Accueil', + stories: 'Vibe Stories', pm: 'Chef de Produit', junior: 'Dév Junior', senior: 'Dév Senior', appendix: 'Annexe', start: 'Commencer' }, + stories: { + cat: 'Vibe Stories', + title: 'Voyez chaque
vous brillant.', + sub: 'Découvrez comment chacun utilise l\'IA pour créer ses propres projets ici.' + }, stage1: { cat: 'Stage 1 · Débutant & PM', title: @@ -1063,12 +1117,18 @@ const i18n = { nav: { title: 'Easy-Vibe Tutorial', home: 'Startseite', + stories: 'Vibe Stories', pm: 'Produktmanager', junior: 'Junior Dev', senior: 'Senior Dev', appendix: 'Anhang', start: 'Starten' }, + stories: { + cat: 'Vibe Stories', + title: 'Sehen Sie jeden
strahlenden Sie.', + sub: 'Entdecken Sie hier, wie jeder KI nutzt, um eigene Projekte zu erstellen.' + }, stage1: { cat: 'Stage 1 · Anfänger & PM', title: @@ -1184,14 +1244,20 @@ const i18n = { }, 'ar-sa': { nav: { - title: 'Easy-Vibe درس تعليمي', + title: 'دليل Easy-Vibe', home: 'الرئيسية', + stories: 'Vibe Stories', pm: 'مدير المنتج', junior: 'مطور مبتدئ', senior: 'مطور خبير', appendix: 'ملحق', start: 'ابدأ التعلم' }, + stories: { + cat: 'Vibe Stories', + title: 'شاهد كل
أنت متألق.', + sub: 'اكتشف كيف يستخدم الجميع الذكاء الاصطناعي لإنشاء مشاريعهم الخاصة هنا.' + }, stage1: { cat: 'Stage 1 · مدير المنتج', title: @@ -1310,12 +1376,18 @@ const i18n = { nav: { title: 'Hướng dẫn Easy-Vibe', home: 'Trang chủ', + stories: 'Vibe Stories', pm: 'Quản lý sản phẩm', junior: 'Dev Sơ/Trung cấp', senior: 'Dev Cao cấp', appendix: 'Phụ lục', start: 'Bắt đầu học' }, + stories: { + cat: 'Vibe Stories', + title: 'Thấy mọi
bạn tỏa sáng.', + 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: { cat: 'Stage 1 · Người mới & PM', title: @@ -1437,6 +1509,8 @@ const t = computed(() => { return i18n[code] || i18n['en'] }) +provide('t', t) + const isCjkLocale = computed(() => { const code = lang.value ? lang.value.toLowerCase() : '' if (['zh-cn', 'zh-tw', 'ja-jp', 'ko-kr'].includes(code)) { @@ -1610,6 +1684,27 @@ const toggleLangMenu = () => { 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 currentPath = router.route.path // Find current locale based on path prefix @@ -1628,7 +1723,8 @@ const changeLang = (targetLocale) => { newPath = `/${targetLocale}/` } - router.go(withBase(newPath)) + const hash = window.location.hash || '' + router.go(withBase(`${newPath}${hash}`)) showLangMenu.value = false } @@ -1636,17 +1732,43 @@ const scrollTo = (id) => { if (id === 'home') { window.scrollTo({ top: 0, behavior: 'smooth' }) activeTab.value = 'home' + updateHash('home') + syncTopPromoWithHash() + updateTopPromoVisibility() return } const el = document.getElementById(id) if (el) { - const navHeight = 48 // Approximate nav height - // Use getBoundingClientRect for better accuracy + const navHeight = 48 const elementPosition = el.getBoundingClientRect().top + window.pageYOffset - // Increase buffer to ensure section header is clearly visible below nav - const offset = elementPosition - navHeight - 64 + const extraOffset = id === 'vibe-stories' ? 20 : 40 + const offset = elementPosition - navHeight - extraOffset window.scrollTo({ top: offset, behavior: 'smooth' }) 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 = () => { - if (!pmSection.value) { + if (topPromoDismissed.value) { + topPromoProgress.value = 0 + return + } + if (!vibeStoriesSection.value) { topPromoProgress.value = 1 return } const navHeight = 44 - const sectionTop = pmSection.value.getBoundingClientRect().top + window.pageYOffset + const sectionTop = + vibeStoriesSection.value.getBoundingClientRect().top + window.pageYOffset const endY = sectionTop - navHeight const startY = endY - 96 const scrollY = window.pageYOffset @@ -1673,6 +1800,7 @@ const updateTopPromoVisibility = () => { } if (scrollY >= endY) { topPromoProgress.value = 0 + topPromoDismissed.value = true return } topPromoProgress.value = (endY - scrollY) / (endY - startY) @@ -1765,15 +1893,21 @@ onMounted(() => { return } } + document.addEventListener('click', closeLangMenu) if (appendixWrapper.value) { appendixWrapper.value.addEventListener('scroll', onAppendixScroll) updatePagination() window.addEventListener('resize', updatePagination) } + syncTopPromoWithHash() + window.setTimeout(() => { + scrollToHashTarget('auto') + }, 0) updateTopPromoVisibility() window.addEventListener('scroll', updateTopPromoVisibility, { passive: true }) window.addEventListener('resize', updateTopPromoVisibility) + window.addEventListener('hashchange', scrollToHashTarget) }) onUnmounted(() => { @@ -1796,6 +1930,7 @@ onUnmounted(() => { window.removeEventListener('resize', updatePagination) window.removeEventListener('scroll', updateTopPromoVisibility) window.removeEventListener('resize', updateTopPromoVisibility) + window.removeEventListener('hashchange', scrollToHashTarget) }) // Stage 1: 产品经理 (Web 原型) @@ -2021,6 +2156,13 @@ const appendixCards = [ > {{ t.nav.home }} +