feat(ui): unify site visual language across key pages

This commit is contained in:
zguiyang
2026-03-13 15:26:08 +08:00
parent 33cc9a31b8
commit bf89acbd2c
10 changed files with 335 additions and 617 deletions

View File

@@ -37,6 +37,10 @@ export const translations = {
rights: 'All rights reserved', rights: 'All rights reserved',
}, },
project: { project: {
featured: "Featured",
filter: {
all: "All",
},
tag: { tag: {
business: 'Business Project', business: 'Business Project',
opensource: 'Open Source', opensource: 'Open Source',
@@ -111,6 +115,10 @@ export const translations = {
rights: '版权所有', rights: '版权所有',
}, },
project: { project: {
featured: "精选",
filter: {
all: "全部",
},
tag: { tag: {
business: '商业项目', business: '商业项目',
opensource: '开源项目', opensource: '开源项目',
@@ -192,4 +200,4 @@ export function generateFlatTranslations() {
/** /**
* Flattened translations for faster lookups * Flattened translations for faster lookups
*/ */
export const flatTranslations = generateFlatTranslations(); export const flatTranslations = generateFlatTranslations();

View File

@@ -17,14 +17,14 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
<main class="min-h-screen"> <main class="min-h-screen">
<!-- Hire Page Hero --> <!-- Hire Page Hero -->
<section class="py-24 relative overflow-hidden"> <section class="py-24 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 via-primary/10 to-blue-900/20 dark:from-blue-900/30 dark:via-primary/20 dark:to-blue-900/30"></div> <div class="page-hero-overlay"></div>
<Container className="relative z-10"> <Container className="relative z-10">
<div class="text-center mb-16"> <div class="text-center mb-16">
<span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4"> <span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4">
{lang === 'zh' ? '专业合作' : 'Professional Collaboration'} {lang === 'zh' ? '专业合作' : 'Professional Collaboration'}
</span> </span>
<h1 class="text-5xl md:text-6xl font-bold mb-6 bg-gradient-to-r from-gray-900 via-primary to-primary dark:from-white dark:via-blue-200 dark:to-orange-300 bg-clip-text text-transparent"> <h1 class="page-title-gradient text-5xl md:text-6xl font-bold mb-6">
{lang === 'zh' ? '合作' : 'Hire Me'} {lang === 'zh' ? '合作' : 'Hire Me'}
</h1> </h1>
<p class="text-lg text-muted-foreground max-w-2xl mx-auto"> <p class="text-lg text-muted-foreground max-w-2xl mx-auto">
@@ -50,7 +50,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</div> </div>
<div class="grid md:grid-cols-2 gap-6"> <div class="grid md:grid-cols-2 gap-6">
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/20 p-6"> <div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
{lang === 'zh' ? '初创公司' : 'Startups'} {lang === 'zh' ? '初创公司' : 'Startups'}
</h3> </h3>
@@ -61,7 +61,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</p> </p>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/20 p-6"> <div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
{lang === 'zh' ? '独立创始人' : 'Indie Hackers'} {lang === 'zh' ? '独立创始人' : 'Indie Hackers'}
</h3> </h3>
@@ -72,7 +72,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</p> </p>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/20 p-6"> <div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
{lang === 'zh' ? '传统企业' : 'Traditional Businesses'} {lang === 'zh' ? '传统企业' : 'Traditional Businesses'}
</h3> </h3>
@@ -83,7 +83,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</p> </p>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/20 p-6"> <div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
{lang === 'zh' ? '技术团队' : 'Technical Teams'} {lang === 'zh' ? '技术团队' : 'Technical Teams'}
</h3> </h3>
@@ -99,7 +99,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</section> </section>
<!-- What I Build --> <!-- What I Build -->
<section class="py-16 relative bg-white/5 dark:bg-gray-900/50"> <section class="py-16 relative page-section-soft">
<Container> <Container>
<div class="max-w-4xl mx-auto"> <div class="max-w-4xl mx-auto">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-4 mb-8">
@@ -112,7 +112,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-blue-500/20 p-8"> <div class="page-surface p-8">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-primary to-blue-600 rounded-xl flex items-center justify-center flex-shrink-0"> <div class="w-12 h-12 bg-gradient-to-br from-primary to-blue-600 rounded-xl flex items-center justify-center flex-shrink-0">
<span class="text-2xl">⚡</span> <span class="text-2xl">⚡</span>
@@ -128,7 +128,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</div> </div>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-blue-500/20 p-8"> <div class="page-surface p-8">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-green-600 to-teal-600 rounded-xl flex items-center justify-center flex-shrink-0"> <div class="w-12 h-12 bg-gradient-to-br from-green-600 to-teal-600 rounded-xl flex items-center justify-center flex-shrink-0">
<span class="text-2xl">🌐</span> <span class="text-2xl">🌐</span>
@@ -144,7 +144,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</div> </div>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-blue-500/20 p-8"> <div class="page-surface p-8">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-orange-600 to-red-600 rounded-xl flex items-center justify-center flex-shrink-0"> <div class="w-12 h-12 bg-gradient-to-br from-orange-600 to-red-600 rounded-xl flex items-center justify-center flex-shrink-0">
<span class="text-2xl">📱</span> <span class="text-2xl">📱</span>
@@ -178,7 +178,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</div> </div>
<div class="grid md:grid-cols-3 gap-6"> <div class="grid md:grid-cols-3 gap-6">
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-green-500/20 p-6 text-center"> <div class="page-surface p-6 text-center">
<div class="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center mx-auto mb-4"> <div class="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center mx-auto mb-4">
<span class="text-2xl">🤝</span> <span class="text-2xl">🤝</span>
</div> </div>
@@ -192,7 +192,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</p> </p>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-green-500/20 p-6 text-center"> <div class="page-surface p-6 text-center">
<div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center mx-auto mb-4"> <div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center mx-auto mb-4">
<span class="text-2xl">💰</span> <span class="text-2xl">💰</span>
</div> </div>
@@ -206,7 +206,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</p> </p>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-green-500/20 p-6 text-center"> <div class="page-surface p-6 text-center">
<div class="w-12 h-12 bg-primary/20 rounded-xl flex items-center justify-center mx-auto mb-4"> <div class="w-12 h-12 bg-primary/20 rounded-xl flex items-center justify-center mx-auto mb-4">
<span class="text-2xl">⏰</span> <span class="text-2xl">⏰</span>
</div> </div>
@@ -225,7 +225,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</section> </section>
<!-- Delivery Style --> <!-- Delivery Style -->
<section class="py-16 relative bg-white/5 dark:bg-gray-900/50"> <section class="py-16 relative page-section-soft">
<Container> <Container>
<div class="max-w-4xl mx-auto"> <div class="max-w-4xl mx-auto">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-4 mb-8">
@@ -237,7 +237,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</h2> </h2>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-yellow-500/20 p-8"> <div class="page-surface p-8">
<ul class="space-y-4"> <ul class="space-y-4">
<li class="flex items-start gap-3"> <li class="flex items-start gap-3">
<span class="text-green-500 text-xl">✓</span> <span class="text-green-500 text-xl">✓</span>
@@ -310,7 +310,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</h2> </h2>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-red-500/20 p-8"> <div class="page-surface p-8">
<div class="flex items-center gap-4 mb-6"> <div class="flex items-center gap-4 mb-6">
<div class="w-4 h-4 bg-green-500 rounded-full animate-pulse"></div> <div class="w-4 h-4 bg-green-500 rounded-full animate-pulse"></div>
<span class="text-lg font-semibold"> <span class="text-lg font-semibold">
@@ -339,7 +339,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</section> </section>
<!-- Contact CTA --> <!-- Contact CTA -->
<section class="py-16 relative bg-gradient-to-br from-blue-900/20 to-blue-900/20 dark:from-blue-900/30 dark:to-blue-900/30"> <section class="py-16 relative page-section-soft">
<Container> <Container>
<div class="max-w-4xl mx-auto text-center"> <div class="max-w-4xl mx-auto text-center">
<h2 class="text-3xl font-bold mb-6"> <h2 class="text-3xl font-bold mb-6">

View File

@@ -23,7 +23,7 @@ const pageTitle = t('site.title');
<!-- Hero Section - Inlined Content --> <!-- Hero Section - Inlined Content -->
<section class="py-32 relative overflow-hidden min-h-screen flex flex-col justify-center"> <section class="py-32 relative overflow-hidden min-h-screen flex flex-col justify-center">
<!-- Background gradient --> <!-- Background gradient -->
<div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 via-primary/10 to-orange-900/20 dark:from-blue-900/30 dark:via-primary/20 dark:to-orange-900/30"></div> <div class="page-hero-overlay"></div>
<Container className="relative z-10"> <Container className="relative z-10">
<div class="text-center mb-16"> <div class="text-center mb-16">
@@ -202,7 +202,7 @@ const pageTitle = t('site.title');
<SkillsMarquee lang={lang} client:only="react" /> <SkillsMarquee lang={lang} client:only="react" />
<!-- Featured Project: Elynd --> <!-- Featured Project: Elynd -->
<section class="py-24 bg-gradient-to-br from-blue-900/20 via-primary/10 to-blue-900/20 dark:from-blue-900/30 dark:via-primary/20 dark:to-blue-900/30"> <section class="py-24 page-section-soft">
<Container> <Container>
<div class="text-center mb-12"> <div class="text-center mb-12">
<span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4"> <span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4">
@@ -219,7 +219,7 @@ const pageTitle = t('site.title');
</div> </div>
<div class="max-w-4xl mx-auto"> <div class="max-w-4xl mx-auto">
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/20 overflow-hidden"> <div class="page-surface border-primary/20 overflow-hidden">
<div class="h-64 md:h-80 bg-gradient-to-br from-primary/20 via-blue-600/20 to-primary/20 flex items-center justify-center relative overflow-hidden"> <div class="h-64 md:h-80 bg-gradient-to-br from-primary/20 via-blue-600/20 to-primary/20 flex items-center justify-center relative overflow-hidden">
<div class="absolute inset-0 bg-grid-primary/10 [mask-image:linear-gradient(0deg,white,rgba(255,255,255,0.3))]"></div> <div class="absolute inset-0 bg-grid-primary/10 [mask-image:linear-gradient(0deg,white,rgba(255,255,255,0.3))]"></div>
<div class="text-center z-10"> <div class="text-center z-10">
@@ -471,7 +471,7 @@ const pageTitle = t('site.title');
</div> </div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 items-stretch"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 items-stretch">
<div class="group overflow-hidden border border-primary/20 hover:border-primary/40 h-full flex flex-col transition-transform duration-500 hover:scale-105 bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl relative" style="will-change: transform; transform: translateZ(0);"> <div class="page-surface group overflow-hidden border-primary/20 hover:border-primary/40 h-full flex flex-col transition-transform duration-500 hover:scale-105 rounded-xl relative" style="will-change: transform; transform: translateZ(0);">
<div class="absolute top-4 right-4 z-10"> <div class="absolute top-4 right-4 z-10">
<span class="px-3 py-1 bg-blue-100 dark:bg-blue-800 text-blue-800 dark:text-blue-200 text-xs rounded-full font-medium border border-blue-200 dark:border-blue-700">{t('project.tag.business')}</span> <span class="px-3 py-1 bg-blue-100 dark:bg-blue-800 text-blue-800 dark:text-blue-200 text-xs rounded-full font-medium border border-blue-200 dark:border-blue-700">{t('project.tag.business')}</span>
</div> </div>
@@ -534,7 +534,7 @@ const pageTitle = t('site.title');
</div> </div>
</div> </div>
</div> </div>
<div class="group overflow-hidden border border-primary/20 hover:border-primary/40 h-full flex flex-col transition-transform duration-500 hover:scale-105 bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl relative" style="will-change: transform; transform: translateZ(0);"> <div class="page-surface group overflow-hidden border-primary/20 hover:border-primary/40 h-full flex flex-col transition-transform duration-500 hover:scale-105 rounded-xl relative" style="will-change: transform; transform: translateZ(0);">
<div class="absolute top-4 right-4 z-10"> <div class="absolute top-4 right-4 z-10">
<span class="px-3 py-1 bg-green-100 dark:bg-green-800 text-green-800 dark:text-green-200 text-xs rounded-full font-medium border border-green-200 dark:border-green-700">{t('project.tag.opensource')}</span> <span class="px-3 py-1 bg-green-100 dark:bg-green-800 text-green-800 dark:text-green-200 text-xs rounded-full font-medium border border-green-200 dark:border-green-700">{t('project.tag.opensource')}</span>
</div> </div>
@@ -597,7 +597,7 @@ const pageTitle = t('site.title');
</div> </div>
</div> </div>
<div class="group overflow-hidden border border-primary/20 hover:border-primary/40 h-full flex flex-col transition-transform duration-500 hover:scale-105 bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl relative" style="will-change: transform; transform: translateZ(0);"> <div class="page-surface group overflow-hidden border-primary/20 hover:border-primary/40 h-full flex flex-col transition-transform duration-500 hover:scale-105 rounded-xl relative" style="will-change: transform; transform: translateZ(0);">
<div class="absolute top-4 right-4 z-10"> <div class="absolute top-4 right-4 z-10">
<span class="px-3 py-1 bg-amber-100 dark:bg-amber-800 text-amber-800 dark:text-amber-200 text-xs rounded-full font-medium border border-amber-200 dark:border-amber-700">{t('project.tag.personal')}</span> <span class="px-3 py-1 bg-amber-100 dark:bg-amber-800 text-amber-800 dark:text-amber-200 text-xs rounded-full font-medium border border-amber-200 dark:border-amber-700">{t('project.tag.personal')}</span>
</div> </div>

View File

@@ -17,14 +17,14 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
<main class="min-h-screen"> <main class="min-h-screen">
<!-- Now Page Hero --> <!-- Now Page Hero -->
<section class="py-24 relative overflow-hidden"> <section class="py-24 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 via-primary/10 to-blue-900/20 dark:from-blue-900/30 dark:via-primary/20 dark:to-blue-900/30"></div> <div class="page-hero-overlay"></div>
<Container className="relative z-10"> <Container className="relative z-10">
<div class="text-center mb-16"> <div class="text-center mb-16">
<span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4"> <span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4">
{lang === 'zh' ? '关于我现在正在做什么' : "What's happening now"} {lang === 'zh' ? '关于我现在正在做什么' : "What's happening now"}
</span> </span>
<h1 class="text-5xl md:text-6xl font-bold mb-6 bg-gradient-to-r from-gray-900 via-primary to-primary dark:from-white dark:via-blue-200 dark:to-orange-300 bg-clip-text text-transparent"> <h1 class="page-title-gradient text-5xl md:text-6xl font-bold mb-6">
{lang === 'zh' ? '现在' : 'Now'} {lang === 'zh' ? '现在' : 'Now'}
</h1> </h1>
<p class="text-lg text-muted-foreground max-w-2xl mx-auto"> <p class="text-lg text-muted-foreground max-w-2xl mx-auto">
@@ -49,7 +49,7 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
</h2> </h2>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/20 p-8"> <div class="page-surface p-8">
<div class="flex items-start gap-6"> <div class="flex items-start gap-6">
<div class="w-16 h-16 bg-gradient-to-br from-primary to-blue-600 rounded-xl flex items-center justify-center flex-shrink-0"> <div class="w-16 h-16 bg-gradient-to-br from-primary to-blue-600 rounded-xl flex items-center justify-center flex-shrink-0">
<span class="text-3xl">⚡</span> <span class="text-3xl">⚡</span>
@@ -74,7 +74,7 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
</section> </section>
<!-- Exploring Section --> <!-- Exploring Section -->
<section class="py-16 relative bg-white/5 dark:bg-gray-900/50"> <section class="py-16 relative page-section-soft">
<Container> <Container>
<div class="max-w-4xl mx-auto"> <div class="max-w-4xl mx-auto">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-4 mb-8">
@@ -87,7 +87,7 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
</div> </div>
<div class="grid gap-6"> <div class="grid gap-6">
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-blue-500/20 p-6"> <div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
{lang === 'zh' ? 'AI Agents 与工作流自动化' : 'AI Agents & Workflow Automation'} {lang === 'zh' ? 'AI Agents 与工作流自动化' : 'AI Agents & Workflow Automation'}
</h3> </h3>
@@ -98,7 +98,7 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
</p> </p>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-blue-500/20 p-6"> <div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
{lang === 'zh' ? '产品导向的开发实践' : 'Product-Led Development Practices'} {lang === 'zh' ? '产品导向的开发实践' : 'Product-Led Development Practices'}
</h3> </h3>
@@ -127,7 +127,7 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl border border-green-500/20 p-6 flex items-center gap-4"> <div class="page-surface rounded-xl p-6 flex items-center gap-4">
<div class="w-12 h-12 bg-green-500/20 rounded-lg flex items-center justify-center flex-shrink-0"> <div class="w-12 h-12 bg-green-500/20 rounded-lg flex items-center justify-center flex-shrink-0">
<span class="text-xl">✨</span> <span class="text-xl">✨</span>
</div> </div>
@@ -160,7 +160,7 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
</h2> </h2>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-yellow-500/20 p-8"> <div class="page-surface p-8">
<p class="text-lg text-muted-foreground mb-8"> <p class="text-lg text-muted-foreground mb-8">
{lang === 'zh' {lang === 'zh'
? '我始终对新的合作机会持开放态度。无论你是想要构建产品、寻找技术合伙人,还是需要技术咨询,都可以联系我。' ? '我始终对新的合作机会持开放态度。无论你是想要构建产品、寻找技术合伙人,还是需要技术咨询,都可以联系我。'

View File

@@ -16,32 +16,34 @@ const pageTitle = t('projects.title');
// 根据当前语言获取项目数据 // 根据当前语言获取项目数据
const currentProjects = projects[lang as keyof typeof projects] || projects.en; const currentProjects = projects[lang as keyof typeof projects] || projects.en;
// Separate featured and grouped projects const filterOptions = [
const featuredProject = currentProjects.find(p => p.featured); { key: "all", label: t("project.filter.all") },
const otherProjects = currentProjects.filter(p => !p.featured); { key: "product", label: t("project.type.product") },
{ key: "client", label: t("project.type.client") },
{ key: "experiment", label: t("project.type.experiment") },
];
// Group by type const statusClassMap = {
const productProjects = otherProjects.filter(p => p.type === 'product'); building: "bg-amber-500/15 text-amber-700 dark:text-amber-300 border-amber-500/30",
const clientProjects = otherProjects.filter(p => p.type === 'client'); completed: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 border-emerald-500/30",
const experimentProjects = otherProjects.filter(p => p.type === 'experiment'); archived: "bg-slate-500/15 text-slate-700 dark:text-slate-300 border-slate-500/30",
} as const;
--- ---
<Layout title={pageTitle}> <Layout title={pageTitle}>
<GlassHeader lang={lang} client:only="react" /> <GlassHeader lang={lang} client:only="react" />
<main class="min-h-screen"> <main class="min-h-screen relative overflow-hidden">
<!-- Projects Hero Section --> <div
<section class="py-24 relative overflow-hidden"> aria-hidden="true"
<!-- Background gradient --> class="page-hero-overlay"
<div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 via-primary/10 to-primary/20 dark:from-blue-900/30 dark:via-primary/20 dark:to-primary/30"></div> ></div>
<Container className="relative z-10"> <section class="relative z-10 py-24">
<div class="text-center mb-16"> <Container>
<!-- Main title --> <div class="text-center mb-8">
<h1 class="text-5xl md:text-6xl font-bold mb-6 bg-gradient-to-r from-gray-900 via-primary to-primary dark:from-white dark:via-blue-200 dark:to-orange-300 bg-clip-text text-transparent"> <h1 class="page-title-gradient text-5xl md:text-6xl font-bold mb-6">
{t('projects.title')} {t('projects.title')}
</h1> </h1>
<!-- Description -->
<p class="text-lg text-muted-foreground max-w-3xl mx-auto mb-12 leading-relaxed"> <p class="text-lg text-muted-foreground max-w-3xl mx-auto mb-12 leading-relaxed">
{t('projects.slogan')} {t('projects.description')} {t('projects.slogan')} {t('projects.description')}
</p> </p>
@@ -49,274 +51,119 @@ const experimentProjects = otherProjects.filter(p => p.type === 'experiment');
</Container> </Container>
</section> </section>
<!-- Featured Project Section --> <section class="relative z-10 pb-20" data-projects-root>
{featuredProject && (
<section class="py-12 bg-gradient-to-br from-blue-900/10 to-blue-900/10 dark:from-blue-900/20 dark:to-blue-900/20">
<Container>
<div class="max-w-5xl mx-auto">
<div class="flex items-center gap-4 mb-8">
<span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium">
Featured
</span>
</div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/30 overflow-hidden">
<div class="grid lg:grid-cols-2">
<div class="h-64 lg:h-auto bg-gradient-to-br from-primary/20 via-blue-600/20 to-primary/20 flex items-center justify-center relative overflow-hidden">
<div class="text-8xl">{featuredProject.icon}</div>
</div>
<div class="p-8">
<div class="flex items-center gap-3 mb-4">
<span class="px-3 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm">
{t(`project.status.${featuredProject.status}`)}
</span>
<span class="px-3 py-1 bg-blue-500/20 text-blue-600 dark:text-blue-400 rounded-full text-sm">
{t(`project.type.${featuredProject.type}`)}
</span>
</div>
<h2 class="text-3xl font-bold mb-4">{featuredProject.title}</h2>
<div class="space-y-4 mb-6">
<div>
<span class="text-sm text-muted-foreground">{t('project.role')}:</span>
<p class="font-medium">{featuredProject.role}</p>
</div>
<div>
<span class="text-sm text-muted-foreground">{t('project.impact')}:</span>
<p class="font-medium">{featuredProject.impact}</p>
</div>
</div>
<div class="space-y-2 mb-6">
{featuredProject.description.map((desc) => (
<p class="text-muted-foreground text-sm">{desc}</p>
))}
</div>
<div class="flex flex-wrap gap-2 mb-6">
{featuredProject.tech.map((tech) => (
<span class="px-3 py-1 bg-gray-500/10 dark:bg-gray-400/20 text-gray-600 dark:text-gray-300 text-sm rounded-md border border-gray-500/20 dark:border-gray-400/30">{tech}</span>
))}
</div>
<div class="flex gap-4">
<a href={featuredProject.link} class="bg-primary hover:bg-primary/90 text-white px-6 py-2 rounded-lg font-medium transition-colors inline-flex items-center gap-2">
{t('project.visit')}
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</a>
{featuredProject.links?.github && (
<a href={featuredProject.links.github} target="_blank" rel="noopener noreferrer" class="border border-gray-500 text-gray-600 dark:text-gray-300 hover:bg-gray-500/10 px-6 py-2 rounded-lg font-medium transition-colors inline-flex items-center gap-2">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
GitHub
</a>
)}
</div>
</div>
</div>
</div>
</div>
</Container>
</section>
)}
<!-- Projects by Type -->
<section class="py-12 relative">
<Container> <Container>
<!-- Product Projects --> <div class="max-w-5xl mx-auto">
{productProjects.length > 0 && ( <div class="page-surface flex flex-wrap gap-2 mb-8 p-1 rounded-xl">
<> {filterOptions.map((option, index) => (
<div class="flex items-center gap-4 mb-8"> <button
<div class="w-10 h-10 bg-primary/20 rounded-lg flex items-center justify-center"> type="button"
<span class="text-xl">🛍️</span> data-filter={option.key}
</div> aria-pressed={index === 0 ? "true" : "false"}
<h2 class="text-2xl font-bold">{t('project.type.product')}</h2> class={`rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
</div> index === 0
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12"> ? "bg-primary text-primary-foreground"
{productProjects.map((project) => ( : "text-muted-foreground hover:text-foreground hover:bg-muted"
<div class={`group overflow-hidden border border-${project.color}-500/20 hover:border-${project.color}-500/40 h-full flex flex-col transition-all duration-500 hover:scale-105 bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl relative`}> }`}
<div class="absolute top-4 right-4 z-10 flex gap-2"> >
<span class={`px-3 py-1 bg-${project.color}-100 dark:bg-${project.color}-800 text-${project.color}-800 dark:text-${project.color}-200 text-xs rounded-full font-medium border border-${project.color}-200 dark:border-${project.color}-700`}>{t(`project.status.${project.status}`)}</span> {option.label}
</button>
))}
</div>
<div class="grid gap-5 md:grid-cols-2">
{currentProjects.map((project) => (
<article
data-project-card
data-type={project.type}
class="page-surface p-6 transition-all duration-300 hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-md"
>
<div class="mb-4 flex items-start justify-between gap-3">
<div>
<h2 class="text-xl font-semibold flex items-center gap-2">
<span>{project.icon}</span>
<span>{project.title}</span>
</h2>
<p class="mt-1 text-sm text-muted-foreground">{t(`project.type.${project.type}`)}</p>
</div> </div>
<div class="flex flex-col items-end gap-2">
<div class={`h-48 bg-gradient-to-br ${project.image.bg} relative overflow-hidden`}> {project.featured && (
<div class={`absolute inset-0 bg-gradient-to-br ${project.image.bg} group-hover:${project.image.hover} transition-all duration-500`}></div> <span class="rounded-full border border-primary/40 bg-primary/15 px-2.5 py-1 text-xs font-medium text-primary">
<div class="absolute bottom-4 left-4 right-4"> {t("project.featured")}
<div class={`bg-black/50 backdrop-blur-sm rounded px-3 py-1 text-xs font-mono ${project.image.text}`}> </span>
{project.title} )}
</div> <span class={`rounded-full border px-2.5 py-1 text-xs font-medium ${statusClassMap[project.status as keyof typeof statusClassMap] ?? statusClassMap.completed}`}>
</div> {t(`project.status.${project.status}`)}
</div> </span>
<div class="p-6 pb-4">
<h3 class={`text-xl group-hover:text-${project.color}-400 transition-colors duration-300 flex items-center gap-2 font-bold mb-4`}>
<span class={`text-${project.color}-500`}>{project.icon}</span>
{project.title}
</h3>
</div>
<div class="px-6 flex-grow">
<div class="space-y-3">
{project.description.slice(0, 2).map((desc) => (
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class={`text-${project.color}-500 mt-1`}>•</span>
<span>{desc}</span>
</div>
))}
</div>
<div class="mt-6 mb-4 flex flex-wrap gap-2">
{project.tech.slice(0, 4).map((tech) => (
<span class="px-2 py-1 bg-gray-500/10 dark:bg-gray-400/20 text-gray-600 dark:text-gray-300 text-xs rounded-md border border-gray-500/20 dark:border-gray-400/30">{tech}</span>
))}
</div>
</div>
<div class="p-6 pt-2">
<a href={project.link} class={`inline-flex items-center text-sm font-medium text-${project.color}-500 hover:text-${project.color}-600 dark:hover:text-${project.color}-400 transition-colors duration-300`}>
{t('project.visit')}
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</a>
</div> </div>
</div> </div>
))}
</div>
</>
)}
<!-- Client Projects --> <div class="space-y-1 text-sm text-muted-foreground">
{clientProjects.length > 0 && ( {project.description.slice(0, 2).map((desc) => (
<> <p>{desc}</p>
<div class="flex items-center gap-4 mb-8"> ))}
<div class="w-10 h-10 bg-blue-500/20 rounded-lg flex items-center justify-center">
<span class="text-xl">💼</span>
</div>
<h2 class="text-2xl font-bold">{t('project.type.client')}</h2>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
{clientProjects.map((project) => (
<div class={`group overflow-hidden border border-${project.color}-500/20 hover:border-${project.color}-500/40 h-full flex flex-col transition-all duration-500 hover:scale-105 bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl relative`}>
<div class="absolute top-4 right-4 z-10 flex gap-2">
<span class={`px-3 py-1 bg-${project.color}-100 dark:bg-${project.color}-800 text-${project.color}-800 dark:text-${project.color}-200 text-xs rounded-full font-medium border border-${project.color}-200 dark:border-${project.color}-700`}>{t(`project.status.${project.status}`)}</span>
</div>
<div class={`h-48 bg-gradient-to-br ${project.image.bg} relative overflow-hidden`}>
<div class={`absolute inset-0 bg-gradient-to-br ${project.image.bg} group-hover:${project.image.hover} transition-all duration-500`}></div>
<div class="absolute bottom-4 left-4 right-4">
<div class={`bg-black/50 backdrop-blur-sm rounded px-3 py-1 text-xs font-mono ${project.image.text}`}>
{project.title}
</div>
</div>
</div>
<div class="p-6 pb-4">
<h3 class={`text-xl group-hover:text-${project.color}-400 transition-colors duration-300 flex items-center gap-2 font-bold mb-4`}>
<span class={`text-${project.color}-500`}>{project.icon}</span>
{project.title}
</h3>
</div>
<div class="px-6 flex-grow">
<div class="space-y-3">
{project.description.slice(0, 2).map((desc) => (
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class={`text-${project.color}-500 mt-1`}>•</span>
<span>{desc}</span>
</div>
))}
</div>
<div class="mt-6 mb-4 flex flex-wrap gap-2">
{project.tech.slice(0, 4).map((tech) => (
<span class="px-2 py-1 bg-gray-500/10 dark:bg-gray-400/20 text-gray-600 dark:text-gray-300 text-xs rounded-md border border-gray-500/20 dark:border-gray-400/30">{tech}</span>
))}
</div>
</div>
<div class="p-6 pt-2">
<a href={project.link} class={`inline-flex items-center text-sm font-medium text-${project.color}-500 hover:text-${project.color}-600 dark:hover:text-${project.color}-400 transition-colors duration-300`}>
{t('project.visit')}
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</a>
</div>
</div> </div>
))}
</div>
</>
)}
<!-- Experiment Projects --> <div class="mt-4 flex flex-wrap gap-2">
{experimentProjects.length > 0 && ( {project.tech.slice(0, 4).map((tech) => (
<> <span class="rounded-md border border-border/80 bg-muted/60 px-2 py-1 text-xs text-muted-foreground">{tech}</span>
<div class="flex items-center gap-4 mb-8"> ))}
<div class="w-10 h-10 bg-green-500/20 rounded-lg flex items-center justify-center">
<span class="text-xl">🧪</span>
</div>
<h2 class="text-2xl font-bold">{t('project.type.experiment')}</h2>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
{experimentProjects.map((project) => (
<div class={`group overflow-hidden border border-${project.color}-500/20 hover:border-${project.color}-500/40 h-full flex flex-col transition-all duration-500 hover:scale-105 bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl relative`}>
<div class="absolute top-4 right-4 z-10 flex gap-2">
<span class={`px-3 py-1 bg-${project.color}-100 dark:bg-${project.color}-800 text-${project.color}-800 dark:text-${project.color}-200 text-xs rounded-full font-medium border border-${project.color}-200 dark:border-${project.color}-700`}>{t(`project.status.${project.status}`)}</span>
</div>
<div class={`h-48 bg-gradient-to-br ${project.image.bg} relative overflow-hidden`}>
<div class={`absolute inset-0 bg-gradient-to-br ${project.image.bg} group-hover:${project.image.hover} transition-all duration-500`}></div>
<div class="absolute bottom-4 left-4 right-4">
<div class={`bg-black/50 backdrop-blur-sm rounded px-3 py-1 text-xs font-mono ${project.image.text}`}>
{project.title}
</div>
</div>
</div>
<div class="p-6 pb-4">
<h3 class={`text-xl group-hover:text-${project.color}-400 transition-colors duration-300 flex items-center gap-2 font-bold mb-4`}>
<span class={`text-${project.color}-500`}>{project.icon}</span>
{project.title}
</h3>
</div>
<div class="px-6 flex-grow">
<div class="space-y-3">
{project.description.slice(0, 2).map((desc) => (
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class={`text-${project.color}-500 mt-1`}>•</span>
<span>{desc}</span>
</div>
))}
</div>
<div class="mt-6 mb-4 flex flex-wrap gap-2">
{project.tech.slice(0, 4).map((tech) => (
<span class="px-2 py-1 bg-gray-500/10 dark:bg-gray-400/20 text-gray-600 dark:text-gray-300 text-xs rounded-md border border-gray-500/20 dark:border-gray-400/30">{tech}</span>
))}
</div>
</div>
<div class="p-6 pt-2">
<a href={project.link} class={`inline-flex items-center text-sm font-medium text-${project.color}-500 hover:text-${project.color}-600 dark:hover:text-${project.color}-400 transition-colors duration-300`}>
{t('project.visit')}
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</a>
</div>
</div> </div>
))}
</div> <div class="mt-5 flex items-center gap-4 text-sm font-medium">
</> <a href={project.link} class="text-primary hover:text-primary/80 transition-colors inline-flex items-center gap-1">
)} {t('project.visit')}
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</a>
{project.links?.github && (
<a href={project.links.github} target="_blank" rel="noopener noreferrer" class="text-muted-foreground hover:text-foreground transition-colors">
GitHub
</a>
)}
</div>
</article>
))}
</div>
</div>
</Container> </Container>
</section> </section>
</main> </main>
<Footer lang={lang} client:load /> <Footer lang={lang} client:load />
</Layout> </Layout>
<script>
const projectsRoot = document.querySelector("[data-projects-root]");
if (projectsRoot) {
const filterButtons = Array.from(projectsRoot.querySelectorAll("[data-filter]"));
const projectCards = Array.from(projectsRoot.querySelectorAll("[data-project-card]"));
const applyFilter = (activeFilter) => {
projectCards.forEach((card) => {
const projectType = card.getAttribute("data-type");
const shouldShow = activeFilter === "all" || projectType === activeFilter;
card.classList.toggle("hidden", !shouldShow);
});
filterButtons.forEach((button) => {
const isActive = button.getAttribute("data-filter") === activeFilter;
button.setAttribute("aria-pressed", isActive ? "true" : "false");
button.classList.toggle("bg-primary", isActive);
button.classList.toggle("text-primary-foreground", isActive);
button.classList.toggle("text-muted-foreground", !isActive);
button.classList.toggle("hover:text-foreground", !isActive);
button.classList.toggle("hover:bg-muted", !isActive);
});
};
filterButtons.forEach((button) => {
button.addEventListener("click", () => {
const selectedFilter = button.getAttribute("data-filter") ?? "all";
applyFilter(selectedFilter);
});
});
applyFilter("all");
}
</script>

View File

@@ -17,14 +17,14 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
<main class="min-h-screen"> <main class="min-h-screen">
<!-- Hire Page Hero --> <!-- Hire Page Hero -->
<section class="py-24 relative overflow-hidden"> <section class="py-24 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 via-primary/10 to-blue-900/20 dark:from-blue-900/30 dark:via-primary/20 dark:to-blue-900/30"></div> <div class="page-hero-overlay"></div>
<Container className="relative z-10"> <Container className="relative z-10">
<div class="text-center mb-16"> <div class="text-center mb-16">
<span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4"> <span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4">
专业合作 专业合作
</span> </span>
<h1 class="text-5xl md:text-6xl font-bold mb-6 bg-gradient-to-r from-gray-900 via-primary to-primary dark:from-white dark:via-blue-200 dark:to-orange-300 bg-clip-text text-transparent"> <h1 class="page-title-gradient text-5xl md:text-6xl font-bold mb-6">
合作 合作
</h1> </h1>
<p class="text-lg text-muted-foreground max-w-2xl mx-auto"> <p class="text-lg text-muted-foreground max-w-2xl mx-auto">
@@ -48,7 +48,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</div> </div>
<div class="grid md:grid-cols-2 gap-6"> <div class="grid md:grid-cols-2 gap-6">
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/20 p-6"> <div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
初创公司 初创公司
</h3> </h3>
@@ -57,7 +57,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</p> </p>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/20 p-6"> <div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
独立创始人 独立创始人
</h3> </h3>
@@ -66,7 +66,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</p> </p>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/20 p-6"> <div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
传统企业 传统企业
</h3> </h3>
@@ -75,7 +75,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</p> </p>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/20 p-6"> <div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
技术团队 技术团队
</h3> </h3>
@@ -89,7 +89,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</section> </section>
<!-- What I Build --> <!-- What I Build -->
<section class="py-16 relative bg-white/5 dark:bg-gray-900/50"> <section class="py-16 relative page-section-soft">
<Container> <Container>
<div class="max-w-4xl mx-auto"> <div class="max-w-4xl mx-auto">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-4 mb-8">
@@ -102,7 +102,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-blue-500/20 p-8"> <div class="page-surface p-8">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-primary to-blue-600 rounded-xl flex items-center justify-center flex-shrink-0"> <div class="w-12 h-12 bg-gradient-to-br from-primary to-blue-600 rounded-xl flex items-center justify-center flex-shrink-0">
<span class="text-2xl">⚡</span> <span class="text-2xl">⚡</span>
@@ -116,7 +116,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</div> </div>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-blue-500/20 p-8"> <div class="page-surface p-8">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-green-600 to-teal-600 rounded-xl flex items-center justify-center flex-shrink-0"> <div class="w-12 h-12 bg-gradient-to-br from-green-600 to-teal-600 rounded-xl flex items-center justify-center flex-shrink-0">
<span class="text-2xl">🌐</span> <span class="text-2xl">🌐</span>
@@ -130,7 +130,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</div> </div>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-blue-500/20 p-8"> <div class="page-surface p-8">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<div class="w-12 h-12 bg-gradient-to-br from-orange-600 to-red-600 rounded-xl flex items-center justify-center flex-shrink-0"> <div class="w-12 h-12 bg-gradient-to-br from-orange-600 to-red-600 rounded-xl flex items-center justify-center flex-shrink-0">
<span class="text-2xl">📱</span> <span class="text-2xl">📱</span>
@@ -162,7 +162,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</div> </div>
<div class="grid md:grid-cols-3 gap-6"> <div class="grid md:grid-cols-3 gap-6">
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-green-500/20 p-6 text-center"> <div class="page-surface p-6 text-center">
<div class="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center mx-auto mb-4"> <div class="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center mx-auto mb-4">
<span class="text-2xl">🤝</span> <span class="text-2xl">🤝</span>
</div> </div>
@@ -174,7 +174,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</p> </p>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-green-500/20 p-6 text-center"> <div class="page-surface p-6 text-center">
<div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center mx-auto mb-4"> <div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center mx-auto mb-4">
<span class="text-2xl">💰</span> <span class="text-2xl">💰</span>
</div> </div>
@@ -186,7 +186,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</p> </p>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-green-500/20 p-6 text-center"> <div class="page-surface p-6 text-center">
<div class="w-12 h-12 bg-primary/20 rounded-xl flex items-center justify-center mx-auto mb-4"> <div class="w-12 h-12 bg-primary/20 rounded-xl flex items-center justify-center mx-auto mb-4">
<span class="text-2xl">⏰</span> <span class="text-2xl">⏰</span>
</div> </div>
@@ -203,7 +203,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</section> </section>
<!-- Delivery Style --> <!-- Delivery Style -->
<section class="py-16 relative bg-white/5 dark:bg-gray-900/50"> <section class="py-16 relative page-section-soft">
<Container> <Container>
<div class="max-w-4xl mx-auto"> <div class="max-w-4xl mx-auto">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-4 mb-8">
@@ -215,7 +215,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</h2> </h2>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-yellow-500/20 p-8"> <div class="page-surface p-8">
<ul class="space-y-4"> <ul class="space-y-4">
<li class="flex items-start gap-3"> <li class="flex items-start gap-3">
<span class="text-green-500 text-xl">✓</span> <span class="text-green-500 text-xl">✓</span>
@@ -272,7 +272,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</h2> </h2>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-red-500/20 p-8"> <div class="page-surface p-8">
<div class="flex items-center gap-4 mb-6"> <div class="flex items-center gap-4 mb-6">
<div class="w-4 h-4 bg-green-500 rounded-full animate-pulse"></div> <div class="w-4 h-4 bg-green-500 rounded-full animate-pulse"></div>
<span class="text-lg font-semibold"> <span class="text-lg font-semibold">
@@ -299,7 +299,7 @@ const pageTitle = lang === 'zh' ? '合作' : 'Hire Me';
</section> </section>
<!-- Contact CTA --> <!-- Contact CTA -->
<section class="py-16 relative bg-gradient-to-br from-blue-900/20 to-blue-900/20 dark:from-blue-900/30 dark:to-blue-900/30"> <section class="py-16 relative page-section-soft">
<Container> <Container>
<div class="max-w-4xl mx-auto text-center"> <div class="max-w-4xl mx-auto text-center">
<h2 class="text-3xl font-bold mb-6"> <h2 class="text-3xl font-bold mb-6">

View File

@@ -23,7 +23,7 @@ const pageTitle = t('site.title');
<!-- Hero Section - Inlined Content --> <!-- Hero Section - Inlined Content -->
<section class="py-32 relative overflow-hidden min-h-screen flex flex-col justify-center"> <section class="py-32 relative overflow-hidden min-h-screen flex flex-col justify-center">
<!-- Background gradient --> <!-- Background gradient -->
<div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 via-primary/10 to-orange-900/20 dark:from-blue-900/30 dark:via-primary/20 dark:to-orange-900/30"></div> <div class="page-hero-overlay"></div>
<Container className="relative z-10"> <Container className="relative z-10">
<div class="text-center mb-16"> <div class="text-center mb-16">
@@ -202,7 +202,7 @@ const pageTitle = t('site.title');
<SkillsMarquee lang={lang} client:only="react" /> <SkillsMarquee lang={lang} client:only="react" />
<!-- Featured Project: Elynd --> <!-- Featured Project: Elynd -->
<section class="py-24 bg-gradient-to-br from-blue-900/20 via-primary/10 to-blue-900/20 dark:from-blue-900/30 dark:via-primary/20 dark:to-blue-900/30"> <section class="py-24 page-section-soft">
<Container> <Container>
<div class="text-center mb-12"> <div class="text-center mb-12">
<span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4"> <span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4">
@@ -217,7 +217,7 @@ const pageTitle = t('site.title');
</div> </div>
<div class="max-w-4xl mx-auto"> <div class="max-w-4xl mx-auto">
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/20 overflow-hidden"> <div class="page-surface border-primary/20 overflow-hidden">
<div class="h-64 md:h-80 bg-gradient-to-br from-primary/20 via-blue-600/20 to-primary/20 flex items-center justify-center relative overflow-hidden"> <div class="h-64 md:h-80 bg-gradient-to-br from-primary/20 via-blue-600/20 to-primary/20 flex items-center justify-center relative overflow-hidden">
<div class="absolute inset-0 bg-grid-primary/10 [mask-image:linear-gradient(0deg,white,rgba(255,255,255,0.3))]"></div> <div class="absolute inset-0 bg-grid-primary/10 [mask-image:linear-gradient(0deg,white,rgba(255,255,255,0.3))]"></div>
<div class="text-center z-10"> <div class="text-center z-10">
@@ -470,7 +470,7 @@ const pageTitle = t('site.title');
</div> </div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 items-stretch"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 items-stretch">
<div class="group overflow-hidden border border-primary/20 hover:border-primary/40 h-full flex flex-col transition-all duration-500 hover:scale-105 bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl relative"> <div class="page-surface group overflow-hidden border-primary/20 hover:border-primary/40 h-full flex flex-col transition-all duration-500 hover:scale-105 rounded-xl relative">
<div class="absolute top-4 right-4 z-10"> <div class="absolute top-4 right-4 z-10">
<span class="px-3 py-1 bg-blue-100 dark:bg-blue-800 text-blue-800 dark:text-blue-200 text-xs rounded-full font-medium border border-blue-200 dark:border-blue-700">{t('project.tag.business')}</span> <span class="px-3 py-1 bg-blue-100 dark:bg-blue-800 text-blue-800 dark:text-blue-200 text-xs rounded-full font-medium border border-blue-200 dark:border-blue-700">{t('project.tag.business')}</span>
</div> </div>
@@ -529,7 +529,7 @@ const pageTitle = t('site.title');
</div> </div>
</div> </div>
<div class="group overflow-hidden border border-primary/20 hover:border-primary/40 h-full flex flex-col transition-all duration-500 hover:scale-105 bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl relative"> <div class="page-surface group overflow-hidden border-primary/20 hover:border-primary/40 h-full flex flex-col transition-all duration-500 hover:scale-105 rounded-xl relative">
<div class="absolute top-4 right-4 z-10"> <div class="absolute top-4 right-4 z-10">
<span class="px-3 py-1 bg-green-100 dark:bg-green-800 text-green-800 dark:text-green-200 text-xs rounded-full font-medium border border-green-200 dark:border-green-700">{t('project.tag.opensource')}</span> <span class="px-3 py-1 bg-green-100 dark:bg-green-800 text-green-800 dark:text-green-200 text-xs rounded-full font-medium border border-green-200 dark:border-green-700">{t('project.tag.opensource')}</span>
</div> </div>
@@ -588,7 +588,7 @@ const pageTitle = t('site.title');
</div> </div>
</div> </div>
<div class="group overflow-hidden border border-primary/20 hover:border-primary/40 h-full flex flex-col transition-all duration-500 hover:scale-105 bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl relative"> <div class="page-surface group overflow-hidden border-primary/20 hover:border-primary/40 h-full flex flex-col transition-all duration-500 hover:scale-105 rounded-xl relative">
<div class="absolute top-4 right-4 z-10"> <div class="absolute top-4 right-4 z-10">
<span class="px-3 py-1 bg-amber-100 dark:bg-amber-800 text-amber-800 dark:text-amber-200 text-xs rounded-full font-medium border border-amber-200 dark:border-amber-700">{t('project.tag.personal')}</span> <span class="px-3 py-1 bg-amber-100 dark:bg-amber-800 text-amber-800 dark:text-amber-200 text-xs rounded-full font-medium border border-amber-200 dark:border-amber-700">{t('project.tag.personal')}</span>
</div> </div>
@@ -652,4 +652,4 @@ const pageTitle = t('site.title');
</section> --> </section> -->
</main> </main>
<Footer lang={lang} client:load /> <Footer lang={lang} client:load />
</Layout> </Layout>

View File

@@ -17,14 +17,14 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
<main class="min-h-screen"> <main class="min-h-screen">
<!-- Now Page Hero --> <!-- Now Page Hero -->
<section class="py-24 relative overflow-hidden"> <section class="py-24 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 via-primary/10 to-blue-900/20 dark:from-blue-900/30 dark:via-primary/20 dark:to-blue-900/30"></div> <div class="page-hero-overlay"></div>
<Container className="relative z-10"> <Container className="relative z-10">
<div class="text-center mb-16"> <div class="text-center mb-16">
<span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4"> <span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium mb-4">
关于我现在正在做什么 关于我现在正在做什么
</span> </span>
<h1 class="text-5xl md:text-6xl font-bold mb-6 bg-gradient-to-r from-gray-900 via-primary to-primary dark:from-white dark:via-blue-200 dark:to-orange-300 bg-clip-text text-transparent"> <h1 class="page-title-gradient text-5xl md:text-6xl font-bold mb-6">
现在 现在
</h1> </h1>
<p class="text-lg text-muted-foreground max-w-2xl mx-auto"> <p class="text-lg text-muted-foreground max-w-2xl mx-auto">
@@ -47,7 +47,7 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
</h2> </h2>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/20 p-8"> <div class="page-surface p-8">
<div class="flex items-start gap-6"> <div class="flex items-start gap-6">
<div class="w-16 h-16 bg-gradient-to-br from-primary to-blue-600 rounded-xl flex items-center justify-center flex-shrink-0"> <div class="w-16 h-16 bg-gradient-to-br from-primary to-blue-600 rounded-xl flex items-center justify-center flex-shrink-0">
<span class="text-3xl">⚡</span> <span class="text-3xl">⚡</span>
@@ -70,7 +70,7 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
</section> </section>
<!-- Exploring Section --> <!-- Exploring Section -->
<section class="py-16 relative bg-white/5 dark:bg-gray-900/50"> <section class="py-16 relative page-section-soft">
<Container> <Container>
<div class="max-w-4xl mx-auto"> <div class="max-w-4xl mx-auto">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-4 mb-8">
@@ -83,7 +83,7 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
</div> </div>
<div class="grid gap-6"> <div class="grid gap-6">
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-blue-500/20 p-6"> <div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
AI Agents 与工作流自动化 AI Agents 与工作流自动化
</h3> </h3>
@@ -92,7 +92,7 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
</p> </p>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-blue-500/20 p-6"> <div class="page-surface p-6">
<h3 class="text-lg font-bold mb-3"> <h3 class="text-lg font-bold mb-3">
产品导向的开发实践 产品导向的开发实践
</h3> </h3>
@@ -119,7 +119,7 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl border border-green-500/20 p-6 flex items-center gap-4"> <div class="page-surface rounded-xl p-6 flex items-center gap-4">
<div class="w-12 h-12 bg-green-500/20 rounded-lg flex items-center justify-center flex-shrink-0"> <div class="w-12 h-12 bg-green-500/20 rounded-lg flex items-center justify-center flex-shrink-0">
<span class="text-xl">✨</span> <span class="text-xl">✨</span>
</div> </div>
@@ -150,7 +150,7 @@ const pageTitle = lang === 'zh' ? '现在' : 'Now';
</h2> </h2>
</div> </div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-yellow-500/20 p-8"> <div class="page-surface p-8">
<p class="text-lg text-muted-foreground mb-8"> <p class="text-lg text-muted-foreground mb-8">
我始终对新的合作机会持开放态度。无论你是想要构建产品、寻找技术合伙人,还是需要技术咨询,都可以联系我。 我始终对新的合作机会持开放态度。无论你是想要构建产品、寻找技术合伙人,还是需要技术咨询,都可以联系我。
</p> </p>

View File

@@ -16,307 +16,154 @@ const pageTitle = t('projects.title');
// 根据当前语言获取项目数据 // 根据当前语言获取项目数据
const currentProjects = projects[lang as keyof typeof projects] || projects.en; const currentProjects = projects[lang as keyof typeof projects] || projects.en;
// Separate featured and grouped projects const filterOptions = [
const featuredProject = currentProjects.find(p => p.featured); { key: "all", label: t("project.filter.all") },
const otherProjects = currentProjects.filter(p => !p.featured); { key: "product", label: t("project.type.product") },
{ key: "client", label: t("project.type.client") },
{ key: "experiment", label: t("project.type.experiment") },
];
// Group by type const statusClassMap = {
const productProjects = otherProjects.filter(p => p.type === 'product'); building: "bg-amber-500/15 text-amber-700 dark:text-amber-300 border-amber-500/30",
const clientProjects = otherProjects.filter(p => p.type === 'client'); completed: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 border-emerald-500/30",
const experimentProjects = otherProjects.filter(p => p.type === 'experiment'); archived: "bg-slate-500/15 text-slate-700 dark:text-slate-300 border-slate-500/30",
} as const;
--- ---
<Layout title={pageTitle}> <Layout title={pageTitle}>
<GlassHeader lang={lang} client:only="react" /> <GlassHeader lang={lang} client:only="react" />
<main class="min-h-screen"> <main class="min-h-screen relative overflow-hidden">
<!-- Projects Hero Section --> <div
<section class="py-24 relative overflow-hidden"> aria-hidden="true"
<!-- Background gradient --> class="page-hero-overlay"
<div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 via-primary/10 to-primary/20 dark:from-blue-900/30 dark:via-primary/20 dark:to-primary/30"></div> ></div>
<Container className="relative z-10"> <section class="relative z-10 py-24">
<div class="text-center mb-16"> <Container>
<!-- Main title --> <div class="text-center mb-8">
<h1 class="text-5xl md:text-6xl font-bold mb-6 bg-gradient-to-r from-gray-900 via-primary to-primary dark:from-white dark:via-blue-200 dark:to-orange-300 bg-clip-text text-transparent"> <h1 class="page-title-gradient text-5xl md:text-6xl font-bold mb-6">
{t('projects.title')} {t('projects.title')}
</h1> </h1>
<!-- Description -->
<p class="text-lg text-muted-foreground max-w-3xl mx-auto mb-12 leading-relaxed"> <p class="text-lg text-muted-foreground max-w-3xl mx-auto mb-12 leading-relaxed">
{t('projects.description')} {t('projects.slogan')} {t('projects.description')}
</p> </p>
</div> </div>
</Container> </Container>
</section> </section>
<!-- Featured Project Section --> <section class="relative z-10 pb-20" data-projects-root>
{featuredProject && (
<section class="py-12 bg-gradient-to-br from-blue-900/10 to-blue-900/10 dark:from-blue-900/20 dark:to-blue-900/20">
<Container>
<div class="max-w-5xl mx-auto">
<div class="flex items-center gap-4 mb-8">
<span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium">
Featured 项目
</span>
</div>
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/30 overflow-hidden">
<div class="grid lg:grid-cols-2">
<div class="h-64 lg:h-auto bg-gradient-to-br from-primary/20 via-blue-600/20 to-primary/20 flex items-center justify-center relative overflow-hidden">
<div class="text-8xl">{featuredProject.icon}</div>
</div>
<div class="p-8">
<div class="flex items-center gap-3 mb-4">
<span class="px-3 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm">
{t(`project.status.${featuredProject.status}`)}
</span>
<span class="px-3 py-1 bg-blue-500/20 text-blue-600 dark:text-blue-400 rounded-full text-sm">
{t(`project.type.${featuredProject.type}`)}
</span>
</div>
<h2 class="text-3xl font-bold mb-4">{featuredProject.title}</h2>
<div class="space-y-4 mb-6">
<div>
<span class="text-sm text-muted-foreground">{t('project.role')}:</span>
<p class="font-medium">{featuredProject.role}</p>
</div>
<div>
<span class="text-sm text-muted-foreground">{t('project.impact')}:</span>
<p class="font-medium">{featuredProject.impact}</p>
</div>
</div>
<div class="space-y-2 mb-6">
{featuredProject.description.map((desc) => (
<p class="text-muted-foreground text-sm">{desc}</p>
))}
</div>
<div class="flex flex-wrap gap-2 mb-6">
{featuredProject.tech.map((tech) => (
<span class="px-3 py-1 bg-gray-500/10 dark:bg-gray-400/20 text-gray-600 dark:text-gray-300 text-sm rounded-md border border-gray-500/20 dark:border-gray-400/30">{tech}</span>
))}
</div>
<div class="flex gap-4">
<a href={featuredProject.link} class="bg-primary hover:bg-primary/90 text-white px-6 py-2 rounded-lg font-medium transition-colors inline-flex items-center gap-2">
{t('project.visit')}
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</a>
{featuredProject.links?.github && (
<a href={featuredProject.links.github} target="_blank" rel="noopener noreferrer" class="border border-gray-500 text-gray-600 dark:text-gray-300 hover:bg-gray-500/10 px-6 py-2 rounded-lg font-medium transition-colors inline-flex items-center gap-2">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
GitHub
</a>
)}
</div>
</div>
</div>
</div>
</div>
</Container>
</section>
)}
<!-- Projects by Type -->
<section class="py-12 relative">
<Container> <Container>
<!-- Product Projects --> <div class="max-w-5xl mx-auto">
{productProjects.length > 0 && ( <div class="page-surface flex flex-wrap gap-2 mb-8 p-1 rounded-xl">
<> {filterOptions.map((option, index) => (
<div class="flex items-center gap-4 mb-8"> <button
<div class="w-10 h-10 bg-primary/20 rounded-lg flex items-center justify-center"> type="button"
<span class="text-xl">🛍️</span> data-filter={option.key}
</div> aria-pressed={index === 0 ? "true" : "false"}
<h2 class="text-2xl font-bold">{t('project.type.product')}</h2> class={`rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
</div> index === 0
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12"> ? "bg-primary text-primary-foreground"
{productProjects.map((project) => ( : "text-muted-foreground hover:text-foreground hover:bg-muted"
<div class={`group overflow-hidden border border-${project.color}-500/20 hover:border-${project.color}-500/40 h-full flex flex-col transition-all duration-500 hover:scale-105 bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl relative`}> }`}
<div class="absolute top-4 right-4 z-10 flex gap-2"> >
<span class={`px-3 py-1 bg-${project.color}-100 dark:bg-${project.color}-800 text-${project.color}-800 dark:text-${project.color}-200 text-xs rounded-full font-medium border border-${project.color}-200 dark:border-${project.color}-700`}>{t(`project.status.${project.status}`)}</span> {option.label}
</button>
))}
</div>
<div class="grid gap-5 md:grid-cols-2">
{currentProjects.map((project) => (
<article
data-project-card
data-type={project.type}
class="page-surface p-6 transition-all duration-300 hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-md"
>
<div class="mb-4 flex items-start justify-between gap-3">
<div>
<h2 class="text-xl font-semibold flex items-center gap-2">
<span>{project.icon}</span>
<span>{project.title}</span>
</h2>
<p class="mt-1 text-sm text-muted-foreground">{t(`project.type.${project.type}`)}</p>
</div> </div>
<div class="flex flex-col items-end gap-2">
<div class={`h-48 bg-gradient-to-br ${project.image.bg} relative overflow-hidden`}> {project.featured && (
<div class={`absolute inset-0 bg-gradient-to-br ${project.image.bg} group-hover:${project.image.hover} transition-all duration-500`}></div> <span class="rounded-full border border-primary/40 bg-primary/15 px-2.5 py-1 text-xs font-medium text-primary">
<div class="absolute bottom-4 left-4 right-4"> {t("project.featured")}
<div class={`bg-black/50 backdrop-blur-sm rounded px-3 py-1 text-xs font-mono ${project.image.text}`}> </span>
{project.title} )}
</div> <span class={`rounded-full border px-2.5 py-1 text-xs font-medium ${statusClassMap[project.status as keyof typeof statusClassMap] ?? statusClassMap.completed}`}>
</div> {t(`project.status.${project.status}`)}
</div> </span>
<div class="p-6 pb-4">
<h3 class={`text-xl group-hover:text-${project.color}-400 transition-colors duration-300 flex items-center gap-2 font-bold mb-4`}>
<span class={`text-${project.color}-500`}>{project.icon}</span>
{project.title}
</h3>
</div>
<div class="px-6 flex-grow">
<div class="space-y-3">
{project.description.slice(0, 2).map((desc) => (
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class={`text-${project.color}-500 mt-1`}>•</span>
<span>{desc}</span>
</div>
))}
</div>
<div class="mt-6 mb-4 flex flex-wrap gap-2">
{project.tech.slice(0, 4).map((tech) => (
<span class="px-2 py-1 bg-gray-500/10 dark:bg-gray-400/20 text-gray-600 dark:text-gray-300 text-xs rounded-md border border-gray-500/20 dark:border-gray-400/30">{tech}</span>
))}
</div>
</div>
<div class="p-6 pt-2">
<a href={project.link} class={`inline-flex items-center text-sm font-medium text-${project.color}-500 hover:text-${project.color}-600 dark:hover:text-${project.color}-400 transition-colors duration-300`}>
{t('project.visit')}
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</a>
</div> </div>
</div> </div>
))}
</div>
</>
)}
<!-- Client Projects --> <div class="space-y-1 text-sm text-muted-foreground">
{clientProjects.length > 0 && ( {project.description.slice(0, 2).map((desc) => (
<> <p>{desc}</p>
<div class="flex items-center gap-4 mb-8"> ))}
<div class="w-10 h-10 bg-blue-500/20 rounded-lg flex items-center justify-center">
<span class="text-xl">💼</span>
</div>
<h2 class="text-2xl font-bold">{t('project.type.client')}</h2>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
{clientProjects.map((project) => (
<div class={`group overflow-hidden border border-${project.color}-500/20 hover:border-${project.color}-500/40 h-full flex flex-col transition-all duration-500 hover:scale-105 bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl relative`}>
<div class="absolute top-4 right-4 z-10 flex gap-2">
<span class={`px-3 py-1 bg-${project.color}-100 dark:bg-${project.color}-800 text-${project.color}-800 dark:text-${project.color}-200 text-xs rounded-full font-medium border border-${project.color}-200 dark:border-${project.color}-700`}>{t(`project.status.${project.status}`)}</span>
</div>
<div class={`h-48 bg-gradient-to-br ${project.image.bg} relative overflow-hidden`}>
<div class={`absolute inset-0 bg-gradient-to-br ${project.image.bg} group-hover:${project.image.hover} transition-all duration-500`}></div>
<div class="absolute bottom-4 left-4 right-4">
<div class={`bg-black/50 backdrop-blur-sm rounded px-3 py-1 text-xs font-mono ${project.image.text}`}>
{project.title}
</div>
</div>
</div>
<div class="p-6 pb-4">
<h3 class={`text-xl group-hover:text-${project.color}-400 transition-colors duration-300 flex items-center gap-2 font-bold mb-4`}>
<span class={`text-${project.color}-500`}>{project.icon}</span>
{project.title}
</h3>
</div>
<div class="px-6 flex-grow">
<div class="space-y-3">
{project.description.slice(0, 2).map((desc) => (
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class={`text-${project.color}-500 mt-1`}>•</span>
<span>{desc}</span>
</div>
))}
</div>
<div class="mt-6 mb-4 flex flex-wrap gap-2">
{project.tech.slice(0, 4).map((tech) => (
<span class="px-2 py-1 bg-gray-500/10 dark:bg-gray-400/20 text-gray-600 dark:text-gray-300 text-xs rounded-md border border-gray-500/20 dark:border-gray-400/30">{tech}</span>
))}
</div>
</div>
<div class="p-6 pt-2">
<a href={project.link} class={`inline-flex items-center text-sm font-medium text-${project.color}-500 hover:text-${project.color}-600 dark:hover:text-${project.color}-400 transition-colors duration-300`}>
{t('project.visit')}
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</a>
</div>
</div> </div>
))}
</div>
</>
)}
<!-- Experiment Projects --> <div class="mt-4 flex flex-wrap gap-2">
{experimentProjects.length > 0 && ( {project.tech.slice(0, 4).map((tech) => (
<> <span class="rounded-md border border-border/80 bg-muted/60 px-2 py-1 text-xs text-muted-foreground">{tech}</span>
<div class="flex items-center gap-4 mb-8"> ))}
<div class="w-10 h-10 bg-green-500/20 rounded-lg flex items-center justify-center">
<span class="text-xl">🧪</span>
</div>
<h2 class="text-2xl font-bold">{t('project.type.experiment')}</h2>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
{experimentProjects.map((project) => (
<div class={`group overflow-hidden border border-${project.color}-500/20 hover:border-${project.color}-500/40 h-full flex flex-col transition-all duration-500 hover:scale-105 bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl relative`}>
<div class="absolute top-4 right-4 z-10 flex gap-2">
<span class={`px-3 py-1 bg-${project.color}-100 dark:bg-${project.color}-800 text-${project.color}-800 dark:text-${project.color}-200 text-xs rounded-full font-medium border border-${project.color}-200 dark:border-${project.color}-700`}>{t(`project.status.${project.status}`)}</span>
</div>
<div class={`h-48 bg-gradient-to-br ${project.image.bg} relative overflow-hidden`}>
<div class={`absolute inset-0 bg-gradient-to-br ${project.image.bg} group-hover:${project.image.hover} transition-all duration-500`}></div>
<div class="absolute bottom-4 left-4 right-4">
<div class={`bg-black/50 backdrop-blur-sm rounded px-3 py-1 text-xs font-mono ${project.image.text}`}>
{project.title}
</div>
</div>
</div>
<div class="p-6 pb-4">
<h3 class={`text-xl group-hover:text-${project.color}-400 transition-colors duration-300 flex items-center gap-2 font-bold mb-4`}>
<span class={`text-${project.color}-500`}>{project.icon}</span>
{project.title}
</h3>
</div>
<div class="px-6 flex-grow">
<div class="space-y-3">
{project.description.slice(0, 2).map((desc) => (
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class={`text-${project.color}-500 mt-1`}>•</span>
<span>{desc}</span>
</div>
))}
</div>
<div class="mt-6 mb-4 flex flex-wrap gap-2">
{project.tech.slice(0, 4).map((tech) => (
<span class="px-2 py-1 bg-gray-500/10 dark:bg-gray-400/20 text-gray-600 dark:text-gray-300 text-xs rounded-md border border-gray-500/20 dark:border-gray-400/30">{tech}</span>
))}
</div>
</div>
<div class="p-6 pt-2">
<a href={project.link} class={`inline-flex items-center text-sm font-medium text-${project.color}-500 hover:text-${project.color}-600 dark:hover:text-${project.color}-400 transition-colors duration-300`}>
{t('project.visit')}
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</a>
</div>
</div> </div>
))}
</div> <div class="mt-5 flex items-center gap-4 text-sm font-medium">
</> <a href={project.link} class="text-primary hover:text-primary/80 transition-colors inline-flex items-center gap-1">
)} {t('project.visit')}
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
</svg>
</a>
{project.links?.github && (
<a href={project.links.github} target="_blank" rel="noopener noreferrer" class="text-muted-foreground hover:text-foreground transition-colors">
GitHub
</a>
)}
</div>
</article>
))}
</div>
</div>
</Container> </Container>
</section> </section>
</main> </main>
<Footer lang={lang} client:load /> <Footer lang={lang} client:load />
</Layout> </Layout>
<script>
const projectsRoot = document.querySelector("[data-projects-root]");
if (projectsRoot) {
const filterButtons = Array.from(projectsRoot.querySelectorAll("[data-filter]"));
const projectCards = Array.from(projectsRoot.querySelectorAll("[data-project-card]"));
const applyFilter = (activeFilter) => {
projectCards.forEach((card) => {
const projectType = card.getAttribute("data-type");
const shouldShow = activeFilter === "all" || projectType === activeFilter;
card.classList.toggle("hidden", !shouldShow);
});
filterButtons.forEach((button) => {
const isActive = button.getAttribute("data-filter") === activeFilter;
button.setAttribute("aria-pressed", isActive ? "true" : "false");
button.classList.toggle("bg-primary", isActive);
button.classList.toggle("text-primary-foreground", isActive);
button.classList.toggle("text-muted-foreground", !isActive);
button.classList.toggle("hover:text-foreground", !isActive);
button.classList.toggle("hover:bg-muted", !isActive);
});
};
filterButtons.forEach((button) => {
button.addEventListener("click", () => {
const selectedFilter = button.getAttribute("data-filter") ?? "all";
applyFilter(selectedFilter);
});
});
applyFilter("all");
}
</script>

View File

@@ -195,6 +195,22 @@
/* Animation utilities */ /* Animation utilities */
@layer utilities { @layer utilities {
.page-hero-overlay {
@apply absolute inset-0 pointer-events-none bg-gradient-to-br from-blue-900/20 via-primary/10 to-orange-900/20 dark:from-blue-900/30 dark:via-primary/20 dark:to-orange-900/30;
}
.page-title-gradient {
@apply bg-gradient-to-r from-gray-900 via-primary to-orange-600 dark:from-white dark:via-blue-200 dark:to-orange-300 bg-clip-text text-transparent;
}
.page-surface {
@apply rounded-2xl border border-border/70 bg-background/75 backdrop-blur-sm shadow-sm;
}
.page-section-soft {
@apply bg-primary/5 dark:bg-primary/10;
}
.animate-fade-in { .animate-fade-in {
animation: fadeIn 0.6s ease-out forwards; animation: fadeIn 0.6s ease-out forwards;
} }