feat(03-03): build About page with tech stack badges and Contact page with form
- About: hero bio, 5 tech categories with TechBadge (UCard grid), approach cards, CTA - Contact: hero stats, ContactForm component, contact info from siteConfig, social links, FAQ cards
This commit is contained in:
+150
-3
@@ -1,4 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { techStack } from '~/data/techstack'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
@@ -11,11 +13,156 @@ useSeoMeta({
|
|||||||
ogImageHeight: 630,
|
ogImageHeight: 630,
|
||||||
ogType: 'website',
|
ogType: 'website',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const techCategories = computed(() => [
|
||||||
|
{
|
||||||
|
key: 'programming' as const,
|
||||||
|
title: t('about.skills.programming'),
|
||||||
|
icon: 'i-lucide-code-2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'front' as const,
|
||||||
|
title: t('about.skills.frontend'),
|
||||||
|
icon: 'i-lucide-palette',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'database' as const,
|
||||||
|
title: t('about.skills.backend'),
|
||||||
|
icon: 'i-lucide-database',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'devtools' as const,
|
||||||
|
title: t('about.skills.tools'),
|
||||||
|
icon: 'i-lucide-settings',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const approachCards = computed(() => [
|
||||||
|
{
|
||||||
|
title: t('about.approach.performance.title'),
|
||||||
|
description: t('about.approach.performance.description'),
|
||||||
|
icon: 'i-lucide-zap',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('about.approach.architecture.title'),
|
||||||
|
description: t('about.approach.architecture.description'),
|
||||||
|
icon: 'i-lucide-git-branch',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('about.approach.quality.title'),
|
||||||
|
description: t('about.approach.quality.description'),
|
||||||
|
icon: 'i-lucide-check-circle',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('about.approach.collaboration.title'),
|
||||||
|
description: t('about.approach.collaboration.description'),
|
||||||
|
icon: 'i-lucide-users',
|
||||||
|
},
|
||||||
|
])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
<div>
|
||||||
<h1 class="text-2xl font-bold">{{ t('nav.about') }}</h1>
|
<!-- Hero Section -->
|
||||||
<p class="text-gray-600 dark:text-gray-400 mt-4">Phase 3 content placeholder</p>
|
<section class="py-20 px-4 text-center">
|
||||||
|
<div class="max-w-4xl mx-auto">
|
||||||
|
<h1 class="text-4xl sm:text-5xl font-bold mb-6">
|
||||||
|
{{ t('about.title') }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-xl text-muted mb-8">
|
||||||
|
{{ t('about.subtitle') }}
|
||||||
|
</p>
|
||||||
|
<div class="space-y-4 text-lg text-muted max-w-3xl mx-auto">
|
||||||
|
<p>{{ t('about.intro.content') }}</p>
|
||||||
|
<p>{{ t('about.experience.content') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Skills Section -->
|
||||||
|
<section class="py-16 px-4">
|
||||||
|
<div class="max-w-7xl mx-auto">
|
||||||
|
<div class="text-center mb-12">
|
||||||
|
<h2 class="text-3xl font-bold mb-4">{{ t('about.skills.title') }}</h2>
|
||||||
|
<p class="text-lg text-muted max-w-2xl mx-auto">
|
||||||
|
{{ t('about.subtitle') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tech Categories Grid -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||||
|
<UCard v-for="category in techCategories" :key="category.key">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<UIcon :name="category.icon" class="text-2xl text-primary" />
|
||||||
|
<h3 class="text-xl font-bold">{{ category.title }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-3">
|
||||||
|
<TechBadge
|
||||||
|
v-for="tech in techStack[category.key]"
|
||||||
|
:key="tech.name"
|
||||||
|
:tech="tech"
|
||||||
|
:show-level="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</UCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Operating Systems -->
|
||||||
|
<UCard>
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<UIcon name="i-lucide-monitor" class="text-2xl text-primary" />
|
||||||
|
<h3 class="text-xl font-bold">{{ t('about.skills.systems') }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-3">
|
||||||
|
<TechBadge
|
||||||
|
v-for="tech in techStack.operating_systems"
|
||||||
|
:key="tech.name"
|
||||||
|
:tech="tech"
|
||||||
|
:show-level="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</UCard>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Approach Section -->
|
||||||
|
<section class="py-16 px-4">
|
||||||
|
<div class="max-w-7xl mx-auto">
|
||||||
|
<div class="text-center mb-12">
|
||||||
|
<h2 class="text-3xl font-bold mb-4">{{ t('about.approach.title') }}</h2>
|
||||||
|
<p class="text-lg text-muted max-w-2xl mx-auto">
|
||||||
|
{{ t('about.approach.subtitle') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<UCard v-for="(card, index) in approachCards" :key="index">
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<UIcon :name="card.icon" class="text-2xl text-primary shrink-0 mt-1" />
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold mb-2">{{ card.title }}</h3>
|
||||||
|
<p class="text-muted">{{ card.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</UCard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- CTA Section -->
|
||||||
|
<section class="py-16 px-4 text-center">
|
||||||
|
<div class="max-w-2xl mx-auto">
|
||||||
|
<h2 class="text-3xl font-bold mb-4">{{ t('about.cta.title') }}</h2>
|
||||||
|
<p class="text-lg text-muted mb-8">{{ t('about.cta.description') }}</p>
|
||||||
|
<div class="flex flex-wrap justify-center gap-4">
|
||||||
|
<UButton to="/contact" size="lg">
|
||||||
|
{{ t('about.cta.button') }}
|
||||||
|
</UButton>
|
||||||
|
<UButton to="/projects" size="lg" variant="outline">
|
||||||
|
{{ t('home.cta.viewProjects') }}
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+120
-3
@@ -1,4 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { siteConfig } from '~/data/site'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
@@ -14,8 +16,123 @@ useSeoMeta({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
<div>
|
||||||
<h1 class="text-2xl font-bold">{{ t('nav.contact') }}</h1>
|
<!-- Hero Section -->
|
||||||
<p class="text-gray-600 dark:text-gray-400 mt-4">Phase 3 content placeholder</p>
|
<section class="py-20 px-4 text-center">
|
||||||
|
<div class="max-w-4xl mx-auto">
|
||||||
|
<h1 class="text-4xl sm:text-5xl font-bold mb-6">
|
||||||
|
{{ t('contact.title') }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-xl text-muted mb-8">
|
||||||
|
{{ t('contact.subtitle') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Stats -->
|
||||||
|
<div class="flex flex-wrap justify-center gap-8">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-2xl font-bold text-primary">24-48h</div>
|
||||||
|
<div class="text-sm text-muted">{{ t('contact.stats.responseTime') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-2xl font-bold text-primary">100%</div>
|
||||||
|
<div class="text-sm text-muted">{{ t('contact.stats.satisfaction') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-2xl font-bold text-primary">Remote</div>
|
||||||
|
<div class="text-sm text-muted">{{ t('contact.stats.collaboration') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Two Column Layout -->
|
||||||
|
<section class="py-16 px-4">
|
||||||
|
<div class="max-w-5xl mx-auto grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||||
|
<!-- Left: Contact Form -->
|
||||||
|
<UCard>
|
||||||
|
<template #header>
|
||||||
|
<h2 class="text-2xl font-bold">{{ t('contact.form.title') }}</h2>
|
||||||
|
</template>
|
||||||
|
<ContactForm />
|
||||||
|
</UCard>
|
||||||
|
|
||||||
|
<!-- Right: Contact Info + Social -->
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
<!-- Contact Info -->
|
||||||
|
<UCard>
|
||||||
|
<template #header>
|
||||||
|
<h2 class="text-xl font-bold">{{ t('contact.quickContact') }}</h2>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<a :href="`mailto:${siteConfig.contact.email}`" class="flex items-center gap-3 hover:text-primary transition-colors">
|
||||||
|
<UIcon name="i-lucide-mail" class="text-xl text-primary shrink-0" />
|
||||||
|
<span>{{ siteConfig.contact.email }}</span>
|
||||||
|
</a>
|
||||||
|
<a :href="`tel:${siteConfig.contact.phone.replace(/\s/g, '')}`" class="flex items-center gap-3 hover:text-primary transition-colors">
|
||||||
|
<UIcon name="i-lucide-phone" class="text-xl text-primary shrink-0" />
|
||||||
|
<span>{{ siteConfig.contact.phone }}</span>
|
||||||
|
</a>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<UIcon name="i-lucide-map-pin" class="text-xl text-primary shrink-0" />
|
||||||
|
<span>{{ siteConfig.contact.location }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</UCard>
|
||||||
|
|
||||||
|
<!-- Social Links -->
|
||||||
|
<UCard>
|
||||||
|
<template #header>
|
||||||
|
<h2 class="text-xl font-bold">{{ t('contact.findMeOn') }}</h2>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<a
|
||||||
|
v-for="social in siteConfig.social.filter(s => s.icon !== 'i-lucide-mail')"
|
||||||
|
:key="social.name"
|
||||||
|
:href="social.url"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="flex items-center gap-3 hover:text-primary transition-colors"
|
||||||
|
>
|
||||||
|
<UIcon :name="social.icon" class="text-xl shrink-0" />
|
||||||
|
<span>{{ social.name }}</span>
|
||||||
|
<UIcon name="i-lucide-external-link" class="text-sm text-muted ml-auto" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</UCard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- FAQ Info Cards -->
|
||||||
|
<section class="py-16 px-4">
|
||||||
|
<div class="max-w-5xl mx-auto">
|
||||||
|
<div class="text-center mb-12">
|
||||||
|
<h2 class="text-3xl font-bold mb-4">{{ t('contact.faq.title') }}</h2>
|
||||||
|
<p class="text-lg text-muted max-w-2xl mx-auto">
|
||||||
|
{{ t('contact.faq.subtitle') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<UCard class="text-center">
|
||||||
|
<UIcon name="i-lucide-clock" class="text-3xl text-primary mb-4 mx-auto" />
|
||||||
|
<h3 class="text-lg font-bold mb-2">{{ t('contact.faq.responseTime.title') }}</h3>
|
||||||
|
<p class="text-muted text-sm">{{ t('contact.faq.responseTime.description') }}</p>
|
||||||
|
</UCard>
|
||||||
|
|
||||||
|
<UCard class="text-center">
|
||||||
|
<UIcon name="i-lucide-building" class="text-3xl text-primary mb-4 mx-auto" />
|
||||||
|
<h3 class="text-lg font-bold mb-2">{{ t('contact.faq.projectTypes.title') }}</h3>
|
||||||
|
<p class="text-muted text-sm">{{ t('contact.faq.projectTypes.description') }}</p>
|
||||||
|
</UCard>
|
||||||
|
|
||||||
|
<UCard class="text-center">
|
||||||
|
<UIcon name="i-lucide-users" class="text-3xl text-primary mb-4 mx-auto" />
|
||||||
|
<h3 class="text-lg font-bold mb-2">{{ t('contact.faq.collaboration.title') }}</h3>
|
||||||
|
<p class="text-muted text-sm">{{ t('contact.faq.collaboration.description') }}</p>
|
||||||
|
</UCard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user