feat(portfolio): redesign site as opportunity gateway
- add now/hire pages and update IA/navigation - feature Elynd in projects and homepage messaging - refresh services/blog/brand copy for AI product positioning - reduce hydration overhead by using Astro Container - remove Google Fonts external dependency and use local fallback stack
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import Layout from "@/layouts/Layout.astro";
|
||||
import GlassHeader from "@/components/GlassHeader";
|
||||
import Footer from "@/components/Footer";
|
||||
import Container from "@/components/ui/Container";
|
||||
import Container from "@/components/ui/Container.astro";
|
||||
import { useTranslations } from "@/i18n/utils";
|
||||
import type { Lang } from "@/types/i18n";
|
||||
import { defaultLang } from "@/i18n/ui";
|
||||
@@ -15,6 +15,15 @@ const pageTitle = t('projects.title');
|
||||
|
||||
// 根据当前语言获取项目数据
|
||||
const currentProjects = projects[lang as keyof typeof projects] || projects.en;
|
||||
|
||||
// Separate featured and grouped projects
|
||||
const featuredProject = currentProjects.find(p => p.featured);
|
||||
const otherProjects = currentProjects.filter(p => !p.featured);
|
||||
|
||||
// Group by type
|
||||
const productProjects = otherProjects.filter(p => p.type === 'product');
|
||||
const clientProjects = otherProjects.filter(p => p.type === 'client');
|
||||
const experimentProjects = otherProjects.filter(p => p.type === 'experiment');
|
||||
---
|
||||
|
||||
<Layout title={pageTitle}>
|
||||
@@ -24,8 +33,8 @@ const currentProjects = projects[lang as keyof typeof projects] || projects.en;
|
||||
<section class="py-24 relative overflow-hidden">
|
||||
<!-- Background gradient -->
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-purple-900/20 via-purple-800/10 to-purple-700/20 dark:from-purple-900/30 dark:via-purple-800/20 dark:to-purple-700/30"></div>
|
||||
|
||||
<Container client:load className="relative z-10">
|
||||
|
||||
<Container className="relative z-10">
|
||||
<div class="text-center mb-16">
|
||||
<!-- Main title -->
|
||||
<h1 class="text-5xl md:text-6xl font-bold mb-6 bg-gradient-to-r from-gray-900 via-purple-600 to-purple-800 dark:from-white dark:via-purple-200 dark:to-purple-300 bg-clip-text text-transparent">
|
||||
@@ -40,69 +49,274 @@ const currentProjects = projects[lang as keyof typeof projects] || projects.en;
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
<!-- Projects Grid Section -->
|
||||
<section class="py-12 relative">
|
||||
<!-- Background gradient -->
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-transparent via-purple-900/5 to-transparent"></div>
|
||||
|
||||
<Container client:load className="relative z-10">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 items-stretch">
|
||||
{currentProjects.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`}>
|
||||
<!-- Project tag - positioned at top right -->
|
||||
<div class="absolute top-4 right-4 z-10">
|
||||
<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.tag.${project.tag}`)}</span>
|
||||
</div>
|
||||
|
||||
<!-- Project image placeholder -->
|
||||
<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}
|
||||
<!-- Featured Project Section -->
|
||||
{featuredProject && (
|
||||
<section class="py-12 bg-gradient-to-br from-purple-900/10 to-blue-900/10 dark:from-purple-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-purple-500/20 text-purple-600 dark:text-purple-400 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-purple-500/30 overflow-hidden">
|
||||
<div class="grid lg:grid-cols-2">
|
||||
<div class="h-64 lg:h-auto bg-gradient-to-br from-purple-600/20 via-blue-600/20 to-purple-600/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-purple-500/20 text-purple-600 dark:text-purple-400 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-purple-500 hover:bg-purple-600 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 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.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>
|
||||
|
||||
<!-- Tech stack indicators with unified gray styling -->
|
||||
<div class="mt-6 mb-4 flex flex-wrap gap-2">
|
||||
{project.tech.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>
|
||||
|
||||
<!-- Visit project link -->
|
||||
<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>
|
||||
</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-purple-500/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 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>
|
||||
</section>
|
||||
</main>
|
||||
<Footer lang={lang} client:load />
|
||||
</Layout>
|
||||
</Layout>
|
||||
|
||||
Reference in New Issue
Block a user