refactor: split HomeFeatures.vue into sub-components for maintainability
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,324 @@
|
||||
<script setup>
|
||||
import { inject, ref, onMounted, onUnmounted } from 'vue'
|
||||
import { withBase } from 'vitepress'
|
||||
import './HomeSection.css'
|
||||
|
||||
const t = inject('t')
|
||||
|
||||
const appendixWrapper = ref(null)
|
||||
const totalPages = ref(1)
|
||||
const currentPage = ref(0)
|
||||
|
||||
const updatePagination = () => {
|
||||
if (appendixWrapper.value) {
|
||||
const { scrollLeft, clientWidth, scrollWidth } = appendixWrapper.value
|
||||
if (scrollWidth <= clientWidth + 5) {
|
||||
totalPages.value = 1
|
||||
currentPage.value = 0
|
||||
} else {
|
||||
totalPages.value = Math.ceil(scrollWidth / clientWidth)
|
||||
currentPage.value = Math.round(scrollLeft / clientWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onAppendixScroll = () => {
|
||||
if (!appendixWrapper.value) return
|
||||
const { scrollLeft, clientWidth } = appendixWrapper.value
|
||||
const newPage = Math.round(scrollLeft / clientWidth)
|
||||
if (currentPage.value !== newPage) {
|
||||
currentPage.value = newPage
|
||||
}
|
||||
}
|
||||
|
||||
const scrollToPage = (pageIndex) => {
|
||||
if (appendixWrapper.value) {
|
||||
const width = appendixWrapper.value.clientWidth
|
||||
appendixWrapper.value.scrollTo({
|
||||
left: pageIndex * width,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const scrollAppendixByPage = (direction) => {
|
||||
const nextPage = Math.min(
|
||||
totalPages.value - 1,
|
||||
Math.max(0, currentPage.value + direction)
|
||||
)
|
||||
scrollToPage(nextPage)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (appendixWrapper.value) {
|
||||
appendixWrapper.value.addEventListener('scroll', onAppendixScroll)
|
||||
updatePagination()
|
||||
window.addEventListener('resize', updatePagination)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (appendixWrapper.value) {
|
||||
appendixWrapper.value.removeEventListener('scroll', onAppendixScroll)
|
||||
}
|
||||
window.removeEventListener('resize', updatePagination)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
id="appendix"
|
||||
class="section-container section-appendix"
|
||||
>
|
||||
<div class="section-header">
|
||||
<h2 class="section-category">
|
||||
{{ t.appendix.cat }}
|
||||
</h2>
|
||||
<h3
|
||||
class="section-headline"
|
||||
v-html="t.appendix.title"
|
||||
/>
|
||||
<p class="section-sub">
|
||||
{{ t.appendix.sub }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="appendixWrapper"
|
||||
class="appendix-scroll-wrapper"
|
||||
>
|
||||
<div class="appendix-track">
|
||||
<a
|
||||
v-for="(card, index) in t.appendix.cards"
|
||||
:key="index"
|
||||
:href="withBase(card.link)"
|
||||
class="appendix-card"
|
||||
>
|
||||
<span class="appendix-emoji">{{
|
||||
['🤖', '🧠', '🎨', '🚀', '⚙️', '💾', '🛠️', '🌐'][index] || '📚'
|
||||
}}</span>
|
||||
<span class="appendix-title">{{ card.title }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="totalPages > 1"
|
||||
class="appendix-scroll-hint"
|
||||
>
|
||||
<div class="appendix-progress-track">
|
||||
<div
|
||||
class="appendix-progress-thumb"
|
||||
:style="{
|
||||
width: `${100 / totalPages}%`,
|
||||
transform: `translateX(${currentPage * 100}%)`
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div class="appendix-scroll-actions">
|
||||
<button
|
||||
class="appendix-arrow-btn"
|
||||
:class="{ disabled: currentPage === 0 }"
|
||||
:disabled="currentPage === 0"
|
||||
aria-label="向左滑动"
|
||||
@click="scrollAppendixByPage(-1)"
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M11.5 5.5L7 10L11.5 14.5"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.4"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="appendix-arrow-btn"
|
||||
:class="{ disabled: currentPage >= totalPages - 1 }"
|
||||
:disabled="currentPage >= totalPages - 1"
|
||||
aria-label="向右滑动"
|
||||
@click="scrollAppendixByPage(1)"
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M8.5 5.5L13 10L8.5 14.5"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.4"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.appendix-scroll-wrapper {
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
margin: 0 -20px;
|
||||
padding: 0 20px 12px;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
overscroll-behavior-x: contain;
|
||||
}
|
||||
|
||||
.appendix-scroll-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.appendix-track {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 40px;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.appendix-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 12px;
|
||||
text-decoration: none !important;
|
||||
color: inherit !important;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
scroll-snap-align: start;
|
||||
width: 120px;
|
||||
min-height: 120px;
|
||||
transition: transform 0.25s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.appendix-card:hover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.appendix-emoji {
|
||||
font-size: 52px;
|
||||
line-height: 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.appendix-title {
|
||||
font-weight: 600;
|
||||
color: #3c3c43;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.35;
|
||||
letter-spacing: -0.01em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.dark .appendix-title {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.appendix-scroll-hint {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 18px;
|
||||
margin-top: 20px;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.appendix-progress-track {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 160px;
|
||||
height: 4px;
|
||||
border-radius: 999px;
|
||||
background: rgba(60, 60, 67, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.appendix-progress-thumb {
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: rgba(60, 60, 67, 0.28);
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
.appendix-scroll-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-left: auto;
|
||||
margin-right: 56px;
|
||||
}
|
||||
|
||||
.appendix-arrow-btn {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(60, 60, 67, 0.05);
|
||||
background: rgba(60, 60, 67, 0.05);
|
||||
color: rgba(60, 60, 67, 0.62);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
color 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
.appendix-arrow-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border-color: rgba(60, 60, 67, 0.08);
|
||||
color: rgba(60, 60, 67, 0.74);
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
.appendix-arrow-btn.disabled,
|
||||
.appendix-arrow-btn:disabled {
|
||||
opacity: 0.42;
|
||||
cursor: default;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.appendix-arrow-btn.disabled:hover,
|
||||
.appendix-arrow-btn:disabled:hover {
|
||||
background: rgba(60, 60, 67, 0.05);
|
||||
color: rgba(60, 60, 67, 0.62);
|
||||
}
|
||||
|
||||
.section-appendix {
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
padding-top: 64px;
|
||||
padding-bottom: 64px;
|
||||
}
|
||||
|
||||
.dark .section-appendix {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.section-appendix {
|
||||
padding-top: 42px;
|
||||
padding-bottom: 42px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,487 @@
|
||||
<script setup>
|
||||
import { computed, inject } from 'vue'
|
||||
import { withBase } from 'vitepress'
|
||||
|
||||
const props = defineProps({
|
||||
isCjkLocale: Boolean
|
||||
})
|
||||
|
||||
const t = inject('t')
|
||||
|
||||
const appleFooterInfo = computed(() => {
|
||||
const locale = t.value._locale || 'zh-cn'
|
||||
const content = {
|
||||
'zh-cn': {
|
||||
notes: [
|
||||
'1. 学习路径与章节内容会持续更新,显示内容以当前页面为准。',
|
||||
'2. 示例项目与截图用于教学演示,可能与后续版本界面存在差异。',
|
||||
'3. 部分章节链接会随着课程迭代调整,建议优先从首页导航进入最新路径。'
|
||||
],
|
||||
breadcrumbPrefix: 'Easy-Vibe',
|
||||
breadcrumbCurrent: '学习导航',
|
||||
columns: [
|
||||
{
|
||||
title: '学习与导航',
|
||||
links: ['零基础入门', '初中级开发', '高级开发', '附录', '学习地图', '课程总览']
|
||||
},
|
||||
{
|
||||
title: '学习支持',
|
||||
links: ['常见问题', '学习建议', '章节勘误', '版本更新']
|
||||
},
|
||||
{
|
||||
title: '项目资源',
|
||||
links: ['GitHub 仓库', '开源协议', '提交 Issue', '贡献指南']
|
||||
},
|
||||
{
|
||||
title: '社区',
|
||||
links: ['学习社群', '讨论区', '课程反馈']
|
||||
},
|
||||
{
|
||||
title: '关于 Easy-Vibe',
|
||||
links: ['项目介绍', '更新日志', '联系我们']
|
||||
}
|
||||
],
|
||||
more: '更多学习方式:访问',
|
||||
moreLink: 'GitHub 仓库',
|
||||
moreTail: ',获取更新与交流信息。',
|
||||
copyright: 'Copyright © 2026 Easy-Vibe. 保留所有权利。',
|
||||
policies: ['隐私政策', '使用条款', '网站地图']
|
||||
},
|
||||
en: {
|
||||
notes: [
|
||||
'1. Learning paths and chapters are continuously updated.',
|
||||
'2. Screenshots and demo projects are for educational illustration.',
|
||||
'3. Some chapter links may change as the course evolves.',
|
||||
'4. The page is optimized for modern desktop browsers and responsive layouts.'
|
||||
],
|
||||
breadcrumbPrefix: 'Easy-Vibe',
|
||||
breadcrumbCurrent: 'Learning Navigation',
|
||||
columns: [
|
||||
{
|
||||
title: 'Explore',
|
||||
links: ['Foundations', 'Junior/Mid Dev', 'Senior Dev', 'Appendix', 'Learning Map', 'Course Outline']
|
||||
},
|
||||
{
|
||||
title: 'Support',
|
||||
links: ['FAQ', 'Learning Tips', 'Errata', 'Release Notes']
|
||||
},
|
||||
{
|
||||
title: 'Resources',
|
||||
links: ['GitHub Repository', 'License', 'Report Issue', 'Contribution Guide']
|
||||
},
|
||||
{
|
||||
title: 'Community',
|
||||
links: ['Community', 'Discussions', 'Feedback']
|
||||
},
|
||||
{
|
||||
title: 'About Easy-Vibe',
|
||||
links: ['Overview', 'Changelog', 'Contact']
|
||||
}
|
||||
],
|
||||
more: 'More ways to learn: visit',
|
||||
moreLink: 'GitHub Repository',
|
||||
moreTail: ' for updates and community discussions.',
|
||||
copyright: 'Copyright © 2026 Easy-Vibe. All rights reserved.',
|
||||
policies: ['Privacy Policy', 'Terms of Use', 'Sitemap']
|
||||
}
|
||||
}
|
||||
return content[locale] || content.en
|
||||
})
|
||||
|
||||
const footerRepositoryLink = 'https://github.com/datawhalechina/easy-vibe'
|
||||
|
||||
const footerPolicyLinkMap = {
|
||||
'隐私政策': '#',
|
||||
'使用条款': '#',
|
||||
'网站地图': '#',
|
||||
'Privacy Policy': '#',
|
||||
'Terms of Use': '#',
|
||||
'Sitemap': '#'
|
||||
}
|
||||
|
||||
const footerColumnLinkMap = {
|
||||
'零基础入门': '/zh-cn/stage-1/',
|
||||
'初中级开发': '/zh-cn/stage-2/',
|
||||
'高级开发': '/zh-cn/stage-3/',
|
||||
'附录': '/zh-cn/appendix/',
|
||||
'学习地图': '/zh-cn/stage-1/learning-map/',
|
||||
'课程总览': '/zh-cn/stage-1/',
|
||||
'GitHub 仓库': 'https://github.com/datawhalechina/easy-vibe',
|
||||
'Foundations': '/en/stage-1/',
|
||||
'Junior/Mid Dev': '/en/stage-2/',
|
||||
'Senior Dev': '/en/stage-3/',
|
||||
'Appendix': '/en/appendix/',
|
||||
'Learning Map': '/en/stage-1/learning-map/',
|
||||
'Course Outline': '/en/stage-1/',
|
||||
'GitHub Repository': 'https://github.com/datawhalechina/easy-vibe',
|
||||
'Overview': '/en/guide/introduction',
|
||||
'Changelog': 'https://github.com/datawhalechina/easy-vibe/releases'
|
||||
}
|
||||
|
||||
const getFooterLink = (label) => {
|
||||
return footerColumnLinkMap[label] || '#'
|
||||
}
|
||||
|
||||
const getPolicyLink = (label) => {
|
||||
return footerPolicyLinkMap[label] || '#'
|
||||
}
|
||||
|
||||
const resolveFooterHref = (link) => {
|
||||
if (link.startsWith('http://') || link.startsWith('https://')) {
|
||||
return link
|
||||
}
|
||||
return withBase(link)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="footer-callout">
|
||||
<h2 v-html="t.footer.title" />
|
||||
<p>{{ t.footer.desc }}</p>
|
||||
<a
|
||||
class="buy-btn large"
|
||||
:href="withBase('/zh-cn/stage-1/learning-map/')"
|
||||
>{{ t.footer.btn }}</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="apple-site-footer"
|
||||
:class="{ 'is-cjk-locale': isCjkLocale }"
|
||||
>
|
||||
<div class="apple-site-footer-inner">
|
||||
<div class="apple-footer-breadcrumb">
|
||||
<span>⌘</span>
|
||||
<span>›</span>
|
||||
<span>{{ appleFooterInfo.breadcrumbPrefix }}</span>
|
||||
<span>›</span>
|
||||
<span>{{ appleFooterInfo.breadcrumbCurrent }}</span>
|
||||
</div>
|
||||
|
||||
<div class="apple-footer-notes">
|
||||
<p
|
||||
v-for="(item, idx) in appleFooterInfo.notes"
|
||||
:key="idx"
|
||||
>
|
||||
{{ item }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="apple-footer-grid">
|
||||
<div
|
||||
v-for="(column, index) in appleFooterInfo.columns"
|
||||
:key="index"
|
||||
class="apple-footer-column"
|
||||
>
|
||||
<h4>{{ column.title }}</h4>
|
||||
<a
|
||||
v-for="(link, linkIndex) in column.links"
|
||||
:key="linkIndex"
|
||||
:href="resolveFooterHref(getFooterLink(link))"
|
||||
>
|
||||
{{ link }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="apple-footer-more">
|
||||
{{ appleFooterInfo.more }}
|
||||
<a :href="footerRepositoryLink">{{ appleFooterInfo.moreLink }}</a>
|
||||
{{ appleFooterInfo.moreTail }}
|
||||
</div>
|
||||
|
||||
<div class="apple-footer-bottom">
|
||||
<p>{{ appleFooterInfo.copyright }}</p>
|
||||
<div class="apple-footer-policy">
|
||||
<a
|
||||
v-for="(policy, policyIndex) in appleFooterInfo.policies"
|
||||
:key="policyIndex"
|
||||
:href="resolveFooterHref(getPolicyLink(policy))"
|
||||
>
|
||||
{{ policy }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.footer-callout {
|
||||
text-align: center;
|
||||
padding: 92px 20px;
|
||||
background: #fff;
|
||||
margin: 0 40px 64px;
|
||||
border-radius: 40px;
|
||||
}
|
||||
|
||||
.dark .footer-callout {
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.footer-callout h2 {
|
||||
font-size: 62px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.03em;
|
||||
color: #1d1d1f;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'SF Pro Display', 'PingFang SC',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.footer-callout p {
|
||||
color: #6e6e73;
|
||||
font-size: 20px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.dark .footer-callout h2 {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.dark .footer-callout p {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.apple-site-footer {
|
||||
max-width: 1060px;
|
||||
margin: 0 auto 56px;
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
.apple-site-footer-inner {
|
||||
border-top: 1px solid #d2d2d7;
|
||||
color: #6e6e73;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.apple-footer-breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #6e6e73;
|
||||
font-size: 12px;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.apple-site-footer.is-cjk-locale .apple-footer-breadcrumb {
|
||||
font-family:
|
||||
'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC',
|
||||
sans-serif;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.apple-footer-notes {
|
||||
padding-top: 18px;
|
||||
}
|
||||
|
||||
.apple-footer-notes p {
|
||||
margin: 0 0 8px;
|
||||
line-height: 1.45;
|
||||
color: #86868b;
|
||||
}
|
||||
|
||||
.apple-site-footer.is-cjk-locale .apple-footer-notes p {
|
||||
font-family:
|
||||
'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC',
|
||||
sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.88;
|
||||
letter-spacing: 0.03em;
|
||||
font-weight: 400;
|
||||
color: #7d7d83;
|
||||
}
|
||||
|
||||
.apple-footer-grid {
|
||||
margin-top: 18px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 22px;
|
||||
}
|
||||
|
||||
.apple-footer-column h4 {
|
||||
margin: 0 0 10px;
|
||||
color: #1d1d1f;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.apple-site-footer.is-cjk-locale .apple-footer-column h4 {
|
||||
font-family:
|
||||
'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC',
|
||||
sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
.apple-footer-column a {
|
||||
display: block;
|
||||
color: #424245;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.apple-site-footer.is-cjk-locale .apple-footer-column a {
|
||||
font-family:
|
||||
'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC',
|
||||
sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.72;
|
||||
letter-spacing: 0.02em;
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
.apple-footer-column a:hover {
|
||||
color: #0066cc;
|
||||
}
|
||||
|
||||
.apple-footer-more {
|
||||
margin-top: 18px;
|
||||
border-top: 1px solid #d2d2d7;
|
||||
padding-top: 14px;
|
||||
color: #6e6e73;
|
||||
}
|
||||
|
||||
.apple-site-footer.is-cjk-locale .apple-footer-more {
|
||||
font-family:
|
||||
'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC',
|
||||
sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.72;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.apple-footer-more a {
|
||||
color: #0066cc;
|
||||
}
|
||||
|
||||
.apple-footer-bottom {
|
||||
margin-top: 14px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid #d2d2d7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.apple-footer-bottom p {
|
||||
margin: 0;
|
||||
color: #86868b;
|
||||
}
|
||||
|
||||
.apple-site-footer.is-cjk-locale .apple-footer-bottom p {
|
||||
font-family:
|
||||
'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC',
|
||||
sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.apple-footer-policy {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.apple-footer-policy a {
|
||||
color: #424245;
|
||||
}
|
||||
|
||||
.apple-footer-policy a:hover {
|
||||
color: #0066cc;
|
||||
}
|
||||
|
||||
.apple-site-footer.is-cjk-locale .apple-footer-policy a {
|
||||
font-family:
|
||||
'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC',
|
||||
sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.apple-site-footer {
|
||||
max-width: 996px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.apple-site-footer-inner {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.apple-footer-notes p {
|
||||
font-size: 11px;
|
||||
line-height: 1.38;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.apple-footer-grid {
|
||||
grid-template-columns: 1.2fr repeat(4, minmax(0, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.apple-footer-column h4 {
|
||||
font-size: 11px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.apple-footer-column a {
|
||||
font-size: 11px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.apple-site-footer.is-cjk-locale .site-footer-inner {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.apple-site-footer.is-cjk-locale .apple-footer-notes p {
|
||||
font-size: 13px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.apple-site-footer.is-cjk-locale .apple-footer-column h4 {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.apple-site-footer.is-cjk-locale .apple-footer-column a {
|
||||
font-size: 13px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.footer-callout {
|
||||
margin: 0 16px 40px;
|
||||
border-radius: 28px;
|
||||
}
|
||||
|
||||
.footer-callout h2 {
|
||||
font-size: 38px;
|
||||
}
|
||||
|
||||
.footer-callout p {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.apple-site-footer {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.apple-footer-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 20px 14px;
|
||||
}
|
||||
|
||||
.apple-footer-bottom {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,204 @@
|
||||
import stage2LovartCover from '../../../../zh-cn/stage-2/frontend/lovart-assets/images/image1.png'
|
||||
import stage2FigmaCover from '../../../../zh-cn/stage-2/frontend/figma-mastergo/images/image8.png'
|
||||
import stage2DesignToCodeCover from '../../../../zh-cn/stage-2/frontend/design-to-code/images/image42.png'
|
||||
import stage2SupabaseCover from '../../../../zh-cn/stage-2/backend/database-supabase/images/image1.png'
|
||||
import stage2ZeaburCover from '../../../../zh-cn/stage-2/backend/zeabur-deployment/images/image1.png'
|
||||
import stage2DifyCover from '../../../../zh-cn/stage-2/ai-capabilities/dify-knowledge-base/images/image1.png'
|
||||
import stage3ElectronCover from '../../../../zh-cn/stage-3/cross-platform/electron-voice-to-text/images/image3.png'
|
||||
import stage3AgentTeamsCover from '../../../../zh-cn/stage-3/core-skills/agent-teams/images/home-cover.svg'
|
||||
import stage3LongRunningCover from '../../../../zh-cn/stage-3/core-skills/long-running-tasks/images/home-cover.svg'
|
||||
import stage3PersonalBrandCover from '../../../../zh-cn/stage-3/personal-brand/personal-website-blog/images/image1.png'
|
||||
|
||||
export const locales = [
|
||||
{ code: 'zh-cn', text: '简体中文' },
|
||||
{ code: 'en', text: 'English' },
|
||||
{ code: 'ja-jp', text: '日本語' },
|
||||
{ code: 'zh-tw', text: '繁體中文' },
|
||||
{ code: 'ko-kr', text: '한국어' },
|
||||
{ code: 'es-es', text: 'Español' },
|
||||
{ code: 'fr-fr', text: 'Français' },
|
||||
{ code: 'de-de', text: 'Deutsch' },
|
||||
{ code: 'ar-sa', text: 'العربية' },
|
||||
{ code: 'vi-vn', text: 'Tiếng Việt' }
|
||||
]
|
||||
|
||||
export const stage1Cards = [
|
||||
{
|
||||
title: 'AI 产品经理',
|
||||
desc: '从想法到高保真原型,你只需要会说话。',
|
||||
sub: '适合非技术背景',
|
||||
color: 'linear-gradient(135deg, #FF9A9E 0%, #FECFEF 99%, #FECFEF 100%)',
|
||||
icon: '🎨',
|
||||
link: '/zh-cn/stage-1/learning-map/'
|
||||
},
|
||||
{
|
||||
title: '游戏化入门',
|
||||
desc: '通过制作贪吃蛇、俄罗斯方块,打破对代码的恐惧。',
|
||||
sub: '边玩边学',
|
||||
color: 'linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%)',
|
||||
icon: '🎮',
|
||||
link: '/zh-cn/stage-1/ai-capabilities-through-games/'
|
||||
},
|
||||
{
|
||||
title: 'Vibe Coding',
|
||||
desc: '掌握 AI 时代的编程核心:提示词工程与上下文管理。',
|
||||
sub: '核心心法',
|
||||
color: 'linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%)',
|
||||
icon: '💡',
|
||||
link: '/zh-cn/stage-1/introduction-to-ai-ide/'
|
||||
}
|
||||
]
|
||||
|
||||
export const stage2Cards = [
|
||||
{
|
||||
imageColor: '#E0C3FC',
|
||||
image: stage2LovartCover,
|
||||
imageAlt: 'Lovart 素材生产 Agent 界面截图',
|
||||
link: '/zh-cn/stage-2/frontend/lovart-assets/'
|
||||
},
|
||||
{
|
||||
imageColor: '#D8C4F8',
|
||||
image: stage2FigmaCover,
|
||||
imageAlt: 'Figma 与 MasterGo 设计工具截图',
|
||||
link: '/zh-cn/stage-2/frontend/figma-mastergo/'
|
||||
},
|
||||
{
|
||||
imageColor: '#C7DDFB',
|
||||
image: stage2DesignToCodeCover,
|
||||
imageAlt: '设计稿转代码示意截图',
|
||||
link: '/zh-cn/stage-2/frontend/design-to-code/'
|
||||
},
|
||||
{
|
||||
imageColor: '#8EC5FC',
|
||||
image: stage2SupabaseCover,
|
||||
imageAlt: 'Supabase 数据库控制台截图',
|
||||
link: '/zh-cn/stage-2/backend/database-supabase/'
|
||||
},
|
||||
{
|
||||
imageColor: '#96E6A1',
|
||||
image: stage2ZeaburCover,
|
||||
imageAlt: 'Zeabur 部署流程截图',
|
||||
link: '/zh-cn/stage-2/backend/zeabur-deployment/'
|
||||
},
|
||||
{
|
||||
imageColor: '#A7F3D0',
|
||||
image: stage2DifyCover,
|
||||
imageAlt: 'Dify 知识库工作台截图',
|
||||
link: '/zh-cn/stage-2/ai-capabilities/dify-knowledge-base/'
|
||||
}
|
||||
]
|
||||
|
||||
export const stage3Cards = [
|
||||
{
|
||||
title: '跨平台桌面应用',
|
||||
desc: '用 Electron 做语音转文字桌面程序,一次开发同时跑在 Windows、macOS、Linux。',
|
||||
tag: 'Stage 3',
|
||||
visualType: 'phone',
|
||||
image: stage3ElectronCover,
|
||||
imageAlt: 'Electron 语音转文字桌面应用预览图',
|
||||
link: '/zh-cn/stage-3/cross-platform/electron-voice-to-text/'
|
||||
},
|
||||
{
|
||||
title: 'AI 智能体团队',
|
||||
desc: '用 Claude Agent Teams 组建 AI 开发小队,多代理协作完成大型任务。',
|
||||
tag: 'Advanced',
|
||||
visualType: 'ai',
|
||||
image: stage3AgentTeamsCover,
|
||||
imageAlt: 'Claude Agent Teams 协作流程封面图',
|
||||
link: '/zh-cn/stage-3/core-skills/agent-teams/'
|
||||
},
|
||||
{
|
||||
title: '长效稳定执行',
|
||||
desc: '用循环脚本和 Ralph 插件管理长时间任务,让 Claude Code 过夜稳定跑完工作。',
|
||||
tag: 'Architecture',
|
||||
visualType: 'arch',
|
||||
image: stage3LongRunningCover,
|
||||
imageAlt: 'Claude Code 长时间执行与循环任务封面图',
|
||||
link: '/zh-cn/stage-3/core-skills/long-running-tasks/'
|
||||
},
|
||||
{
|
||||
title: '个人品牌与输出',
|
||||
desc: '搭建个人网站与技术博客,让你的项目和经验长期沉淀并被更多人看到。',
|
||||
tag: 'Brand',
|
||||
visualType: 'brand',
|
||||
image: stage3PersonalBrandCover,
|
||||
imageAlt: '个人网站与学术博客示例截图',
|
||||
imageClass: 'prod-image--personal-brand',
|
||||
link: '/zh-cn/stage-3/personal-brand/personal-website-blog/'
|
||||
}
|
||||
]
|
||||
|
||||
export const appendixCards = [
|
||||
{
|
||||
title: '人工智能',
|
||||
desc: 'LLM、Agent、RAG,深入 AI 底层原理。',
|
||||
tag: 'AI',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/ai-history'
|
||||
},
|
||||
{
|
||||
title: '提示词工程',
|
||||
desc: '掌握与 AI 高效对话的技巧,解锁潜力。',
|
||||
tag: 'AI',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/prompt-engineering'
|
||||
},
|
||||
{
|
||||
title: '大语言模型',
|
||||
desc: '深入浅出解析 LLM 的工作原理与应用。',
|
||||
tag: 'AI',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/llm-principles'
|
||||
},
|
||||
{
|
||||
title: 'Agent 智能体',
|
||||
desc: '探索具备自主决策与执行能力的 AI 架构。',
|
||||
tag: 'AI',
|
||||
link: '/zh-cn/appendix/8-artificial-intelligence/ai-agents'
|
||||
},
|
||||
{
|
||||
title: '前端基础',
|
||||
desc: 'HTML/CSS/JS 三大基石,入门必修课。',
|
||||
tag: 'Frontend',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/javascript-deep-dive'
|
||||
},
|
||||
{
|
||||
title: '前端进化史',
|
||||
desc: '了解前端技术栈演变,把握发展趋势。',
|
||||
tag: 'Frontend',
|
||||
link: '/zh-cn/appendix/3-browser-and-frontend/frontend-frameworks'
|
||||
},
|
||||
{
|
||||
title: '后端架构',
|
||||
desc: '从单体到微服务,探索架构演进之路。',
|
||||
tag: 'Backend',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/backend-layered-architecture'
|
||||
},
|
||||
{
|
||||
title: '后端语言',
|
||||
desc: '对比主流后端语言特性,选择最佳技术栈。',
|
||||
tag: 'Backend',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/backend-languages'
|
||||
},
|
||||
{
|
||||
title: '数据库原理',
|
||||
desc: '理解数据库核心原理,掌握数据存储艺术。',
|
||||
tag: 'Database',
|
||||
link: '/zh-cn/appendix/5-data/database-fundamentals'
|
||||
},
|
||||
{
|
||||
title: 'API 设计',
|
||||
desc: 'API 接口设计与开发的基础知识。',
|
||||
tag: 'API',
|
||||
link: '/zh-cn/appendix/4-server-and-backend/api-intro'
|
||||
},
|
||||
{
|
||||
title: 'Git 版本控制',
|
||||
desc: '深入理解 Git 原理与高级用法。',
|
||||
tag: 'General',
|
||||
link: '/zh-cn/appendix/2-development-tools/git-version-control'
|
||||
},
|
||||
{
|
||||
title: '计算机网络',
|
||||
desc: '网络协议与通信原理的基础知识。',
|
||||
tag: 'General',
|
||||
link: '/zh-cn/appendix/1-computer-fundamentals/computer-networks'
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,88 @@
|
||||
.section-container {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto 96px;
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 44px;
|
||||
}
|
||||
|
||||
.section-category {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 14px;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: #1d1d1f;
|
||||
letter-spacing: -0.024em;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'SF Pro Display', 'PingFang SC',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.section-headline {
|
||||
font-size: 64px;
|
||||
line-height: 1.08;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.034em;
|
||||
margin-bottom: 12px;
|
||||
color: #1d1d1f;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'SF Pro Display', 'PingFang SC',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.section-sub {
|
||||
font-size: 21px;
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.01em;
|
||||
color: #6e6e73;
|
||||
max-width: 760px;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'SF Pro Text', 'PingFang SC',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.dark .section-category,
|
||||
.dark .section-headline {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.dark .section-sub {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
:is(.feature-card, .comm-card, .prod-card, .appendix-card, .buy-btn) {
|
||||
border-bottom: none !important;
|
||||
outline: none !important;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
:is(
|
||||
.feature-card,
|
||||
.comm-card,
|
||||
.prod-card,
|
||||
.appendix-card,
|
||||
.buy-btn
|
||||
):is(:hover, :focus, :focus-visible, :active) {
|
||||
border-bottom-color: transparent !important;
|
||||
text-decoration: none !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.section-headline {
|
||||
font-size: 42px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<script setup>
|
||||
import { inject } from 'vue'
|
||||
import { withBase } from 'vitepress'
|
||||
import { stage1Cards } from './HomeData'
|
||||
import './HomeSection.css'
|
||||
|
||||
const t = inject('t')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section id="pm" class="section-container section-pm">
|
||||
<div class="section-header">
|
||||
<h2 class="section-category">
|
||||
{{ t.stage1.cat }}
|
||||
</h2>
|
||||
<h3
|
||||
class="section-headline"
|
||||
v-html="t.stage1.title"
|
||||
/>
|
||||
<p class="section-sub">
|
||||
{{ t.stage1.sub }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-grid">
|
||||
<a
|
||||
v-for="(card, i) in stage1Cards"
|
||||
:key="i"
|
||||
:href="withBase(t.stage1.cards[i].link)"
|
||||
class="feature-card glass"
|
||||
>
|
||||
<div
|
||||
class="feature-icon"
|
||||
:style="{ background: card.color }"
|
||||
>
|
||||
{{ card.icon }}
|
||||
</div>
|
||||
<div class="feature-content">
|
||||
<h4>{{ t.stage1.cards[i].title }}</h4>
|
||||
<p>{{ t.stage1.cards[i].desc }}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: #fff;
|
||||
border-radius: 32px;
|
||||
padding: 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
box-shadow: none;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-decoration: none !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
|
||||
.dark .feature-card {
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: scale(1.015);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
|
||||
.feature-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.feature-content h4 {
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
color: #1d1d1f;
|
||||
line-height: 1.3;
|
||||
letter-spacing: -0.024em;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'SF Pro Display', 'PingFang SC',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.feature-content p {
|
||||
font-size: 17px;
|
||||
color: #6e6e73;
|
||||
line-height: 1.6;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dark .feature-content h4 {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.dark .feature-content p {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.feature-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.feature-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.feature-card {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,178 @@
|
||||
<script setup>
|
||||
import { inject, computed } from 'vue'
|
||||
import { withBase } from 'vitepress'
|
||||
import { stage2Cards } from './HomeData'
|
||||
import './HomeSection.css'
|
||||
|
||||
const t = inject('t')
|
||||
|
||||
const localizedStage2Cards = computed(() => {
|
||||
return t.value.stage2.cards.map((card, index) => {
|
||||
const visual = stage2Cards.find((item) => item.link === card.link) || stage2Cards[index]
|
||||
return {
|
||||
...card,
|
||||
...visual
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
id="junior"
|
||||
class="section-container section-junior"
|
||||
>
|
||||
<div class="section-header">
|
||||
<h2 class="section-category">
|
||||
{{ t.stage2.cat }}
|
||||
</h2>
|
||||
<h3
|
||||
class="section-headline"
|
||||
v-html="t.stage2.title"
|
||||
/>
|
||||
<p class="section-sub">
|
||||
{{ t.stage2.sub }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="comm-grid">
|
||||
<a
|
||||
v-for="(card, index) in localizedStage2Cards"
|
||||
:key="index"
|
||||
:href="withBase(card.link)"
|
||||
class="comm-card glass"
|
||||
>
|
||||
<div
|
||||
class="comm-visual"
|
||||
:style="{ backgroundColor: card.imageColor }"
|
||||
>
|
||||
<img
|
||||
:src="card.image"
|
||||
:alt="card.imageAlt || card.title"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
<div class="comm-text">
|
||||
<h4 class="comm-title">{{ card.title }}</h4>
|
||||
<p class="comm-desc">{{ card.desc }}</p>
|
||||
<span class="comm-note">进一步了解 ›</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.section-junior {
|
||||
margin-top: 72px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.section-junior {
|
||||
margin-top: 56px;
|
||||
}
|
||||
}
|
||||
|
||||
.comm-grid {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
overflow-x: auto;
|
||||
width: calc(100% + 40px);
|
||||
margin: 0 -20px;
|
||||
padding: 12px 20px 16px;
|
||||
scroll-snap-type: x mandatory;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-x: contain;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.comm-grid::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comm-card {
|
||||
flex: 0 0 380px;
|
||||
border-radius: 32px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: none;
|
||||
border: 1px solid rgba(0, 0, 0, 0.025);
|
||||
transition: transform 0.3s;
|
||||
transform-origin: center top;
|
||||
display: block;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
.dark .comm-card {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-color: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.comm-card:hover {
|
||||
transform: scale(1.015);
|
||||
}
|
||||
|
||||
.comm-visual {
|
||||
height: 220px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.comm-visual img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
object-position: top center;
|
||||
}
|
||||
|
||||
.comm-text {
|
||||
padding: 26px 26px 30px;
|
||||
}
|
||||
|
||||
.comm-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
color: #1d1d1f;
|
||||
letter-spacing: -0.02em;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'SF Pro Display', 'PingFang SC',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.comm-desc {
|
||||
font-size: 16px;
|
||||
color: #6e6e73;
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.comm-note {
|
||||
font-size: 17px;
|
||||
color: #0066cc;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.dark .comm-title {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.dark .comm-desc {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.comm-card {
|
||||
flex-basis: 340px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.comm-card {
|
||||
flex-basis: min(86vw, 340px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,156 @@
|
||||
<script setup>
|
||||
import { inject } from 'vue'
|
||||
import { withBase } from 'vitepress'
|
||||
import { stage3Cards } from './HomeData'
|
||||
import './HomeSection.css'
|
||||
|
||||
const t = inject('t')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
id="senior"
|
||||
class="section-container section-senior"
|
||||
>
|
||||
<div class="section-header">
|
||||
<h2 class="section-category">
|
||||
{{ t.stage3.cat }}
|
||||
</h2>
|
||||
<h3
|
||||
class="section-headline"
|
||||
v-html="t.stage3.title"
|
||||
/>
|
||||
<p class="section-sub">
|
||||
{{ t.stage3.sub }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="scroll-container">
|
||||
<div class="scroll-track">
|
||||
<a
|
||||
v-for="(card, index) in stage3Cards"
|
||||
:key="index"
|
||||
:href="withBase(t.stage3.cards[index].link)"
|
||||
class="prod-card glass"
|
||||
>
|
||||
<div class="prod-tag">{{ card.tag }}</div>
|
||||
<h4>{{ t.stage3.cards[index].title }}</h4>
|
||||
<p>{{ t.stage3.cards[index].desc }}</p>
|
||||
<div class="prod-visual">
|
||||
<img
|
||||
:src="card.image"
|
||||
:alt="card.imageAlt"
|
||||
:class="card.imageClass"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.scroll-container {
|
||||
overflow-x: auto;
|
||||
padding-bottom: 40px;
|
||||
margin: 0 -20px;
|
||||
padding: 12px 20px 40px;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-snap-type: x mandatory;
|
||||
overscroll-behavior-x: contain;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.scroll-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scroll-track {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.prod-card {
|
||||
width: 300px;
|
||||
height: 400px;
|
||||
border-radius: 32px;
|
||||
background: #f7f7f9;
|
||||
padding: 30px;
|
||||
scroll-snap-align: center;
|
||||
text-decoration: none !important;
|
||||
color: inherit !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: transform 0.3s;
|
||||
transform-origin: center top;
|
||||
border: 1px solid rgba(0, 0, 0, 0.025);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.dark .prod-card {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-color: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.prod-card:hover {
|
||||
transform: scale(1.015);
|
||||
}
|
||||
|
||||
.prod-tag {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #6e6e73;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.prod-card h4 {
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
color: #1d1d1f;
|
||||
letter-spacing: -0.025em;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'SF Pro Display', 'PingFang SC',
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.prod-card p {
|
||||
color: #6e6e73;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.dark .prod-tag,
|
||||
.dark .prod-card p {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.dark .prod-card h4 {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.prod-visual {
|
||||
margin-top: auto;
|
||||
height: 150px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #dbeafe 0%, #e5e7eb 100%);
|
||||
}
|
||||
|
||||
.prod-visual img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.prod-visual img.prod-image--personal-brand {
|
||||
transform: scale(1.18) translateY(-10px);
|
||||
transform-origin: center top;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user