feat(ui): unify site visual language across key pages
This commit is contained in:
@@ -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: '开源项目',
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'
|
||||||
? '我始终对新的合作机会持开放态度。无论你是想要构建产品、寻找技术合伙人,还是需要技术咨询,都可以联系我。'
|
? '我始终对新的合作机会持开放态度。无论你是想要构建产品、寻找技术合伙人,还是需要技术咨询,都可以联系我。'
|
||||||
|
|||||||
@@ -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>
|
<Container>
|
||||||
<div class="max-w-5xl mx-auto">
|
<div class="max-w-5xl mx-auto">
|
||||||
<div class="flex items-center gap-4 mb-8">
|
<div class="page-surface flex flex-wrap gap-2 mb-8 p-1 rounded-xl">
|
||||||
<span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium">
|
{filterOptions.map((option, index) => (
|
||||||
Featured
|
<button
|
||||||
</span>
|
type="button"
|
||||||
</div>
|
data-filter={option.key}
|
||||||
|
aria-pressed={index === 0 ? "true" : "false"}
|
||||||
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/30 overflow-hidden">
|
class={`rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
|
||||||
<div class="grid lg:grid-cols-2">
|
index === 0
|
||||||
<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">
|
? "bg-primary text-primary-foreground"
|
||||||
<div class="text-8xl">{featuredProject.icon}</div>
|
: "text-muted-foreground hover:text-foreground hover:bg-muted"
|
||||||
</div>
|
}`}
|
||||||
<div class="p-8">
|
>
|
||||||
<div class="flex items-center gap-3 mb-4">
|
{option.label}
|
||||||
<span class="px-3 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm">
|
</button>
|
||||||
{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>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2 mb-6">
|
<div class="grid gap-5 md:grid-cols-2">
|
||||||
{featuredProject.tech.map((tech) => (
|
{currentProjects.map((project) => (
|
||||||
<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>
|
<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 class="flex flex-col items-end gap-2">
|
||||||
|
{project.featured && (
|
||||||
|
<span class="rounded-full border border-primary/40 bg-primary/15 px-2.5 py-1 text-xs font-medium text-primary">
|
||||||
|
{t("project.featured")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span class={`rounded-full border px-2.5 py-1 text-xs font-medium ${statusClassMap[project.status as keyof typeof statusClassMap] ?? statusClassMap.completed}`}>
|
||||||
|
{t(`project.status.${project.status}`)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-1 text-sm text-muted-foreground">
|
||||||
|
{project.description.slice(0, 2).map((desc) => (
|
||||||
|
<p>{desc}</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-4">
|
<div class="mt-4 flex flex-wrap gap-2">
|
||||||
<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">
|
{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>
|
||||||
|
|
||||||
|
<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')}
|
{t('project.visit')}
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
{featuredProject.links?.github && (
|
{project.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">
|
<a href={project.links.github} target="_blank" rel="noopener noreferrer" class="text-muted-foreground hover:text-foreground transition-colors">
|
||||||
<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
|
GitHub
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</article>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<!-- Projects by Type -->
|
|
||||||
<section class="py-12 relative">
|
|
||||||
<Container>
|
|
||||||
<!-- Product Projects -->
|
|
||||||
{productProjects.length > 0 && (
|
|
||||||
<>
|
|
||||||
<div class="flex items-center gap-4 mb-8">
|
|
||||||
<div class="w-10 h-10 bg-primary/20 rounded-lg flex items-center justify-center">
|
|
||||||
<span class="text-xl">🛍️</span>
|
|
||||||
</div>
|
|
||||||
<h2 class="text-2xl font-bold">{t('project.type.product')}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
|
|
||||||
{productProjects.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>
|
</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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<!-- Client Projects -->
|
|
||||||
{clientProjects.length > 0 && (
|
|
||||||
<>
|
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<!-- Experiment Projects -->
|
|
||||||
{experimentProjects.length > 0 && (
|
|
||||||
<>
|
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
<Container>
|
||||||
<div class="max-w-5xl mx-auto">
|
<div class="max-w-5xl mx-auto">
|
||||||
<div class="flex items-center gap-4 mb-8">
|
<div class="page-surface flex flex-wrap gap-2 mb-8 p-1 rounded-xl">
|
||||||
<span class="inline-block px-4 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm font-medium">
|
{filterOptions.map((option, index) => (
|
||||||
Featured 项目
|
<button
|
||||||
</span>
|
type="button"
|
||||||
</div>
|
data-filter={option.key}
|
||||||
|
aria-pressed={index === 0 ? "true" : "false"}
|
||||||
<div class="bg-white/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-2xl border border-primary/30 overflow-hidden">
|
class={`rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
|
||||||
<div class="grid lg:grid-cols-2">
|
index === 0
|
||||||
<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">
|
? "bg-primary text-primary-foreground"
|
||||||
<div class="text-8xl">{featuredProject.icon}</div>
|
: "text-muted-foreground hover:text-foreground hover:bg-muted"
|
||||||
</div>
|
}`}
|
||||||
<div class="p-8">
|
>
|
||||||
<div class="flex items-center gap-3 mb-4">
|
{option.label}
|
||||||
<span class="px-3 py-1 bg-primary/20 text-primary dark:text-primary rounded-full text-sm">
|
</button>
|
||||||
{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>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2 mb-6">
|
<div class="grid gap-5 md:grid-cols-2">
|
||||||
{featuredProject.tech.map((tech) => (
|
{currentProjects.map((project) => (
|
||||||
<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>
|
<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 class="flex flex-col items-end gap-2">
|
||||||
|
{project.featured && (
|
||||||
|
<span class="rounded-full border border-primary/40 bg-primary/15 px-2.5 py-1 text-xs font-medium text-primary">
|
||||||
|
{t("project.featured")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span class={`rounded-full border px-2.5 py-1 text-xs font-medium ${statusClassMap[project.status as keyof typeof statusClassMap] ?? statusClassMap.completed}`}>
|
||||||
|
{t(`project.status.${project.status}`)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-1 text-sm text-muted-foreground">
|
||||||
|
{project.description.slice(0, 2).map((desc) => (
|
||||||
|
<p>{desc}</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-4">
|
<div class="mt-4 flex flex-wrap gap-2">
|
||||||
<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">
|
{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>
|
||||||
|
|
||||||
|
<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')}
|
{t('project.visit')}
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
{featuredProject.links?.github && (
|
{project.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">
|
<a href={project.links.github} target="_blank" rel="noopener noreferrer" class="text-muted-foreground hover:text-foreground transition-colors">
|
||||||
<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
|
GitHub
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</article>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<!-- Projects by Type -->
|
|
||||||
<section class="py-12 relative">
|
|
||||||
<Container>
|
|
||||||
<!-- Product Projects -->
|
|
||||||
{productProjects.length > 0 && (
|
|
||||||
<>
|
|
||||||
<div class="flex items-center gap-4 mb-8">
|
|
||||||
<div class="w-10 h-10 bg-primary/20 rounded-lg flex items-center justify-center">
|
|
||||||
<span class="text-xl">🛍️</span>
|
|
||||||
</div>
|
|
||||||
<h2 class="text-2xl font-bold">{t('project.type.product')}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
|
|
||||||
{productProjects.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>
|
</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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<!-- Client Projects -->
|
|
||||||
{clientProjects.length > 0 && (
|
|
||||||
<>
|
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<!-- Experiment Projects -->
|
|
||||||
{experimentProjects.length > 0 && (
|
|
||||||
<>
|
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user