refactor(i18n): restructure translation system with nested objects and proxy

feat(data): add projects and services data structure for dynamic rendering
refactor(pages): replace hardcoded content with dynamic data-driven components
This commit is contained in:
joyzhao
2025-06-19 10:52:05 +08:00
parent c1f240d007
commit 5b6b9f5d58
7 changed files with 446 additions and 541 deletions

View File

@@ -6,53 +6,119 @@ export const languages = {
export const defaultLang = 'en'; export const defaultLang = 'en';
export const ui = { // 定义嵌套结构的国际化数据
const structuredUi = {
en: { en: {
'nav.home': 'Home', nav: {
'nav.about': 'About', home: 'Home',
'nav.services': 'Services', about: 'About',
'nav.projects': 'Projects', services: 'Services',
'nav.blog': 'Blog', projects: 'Projects',
'nav.contact': 'Contact', blog: 'Blog',
'site.title': 'Joy Zhao - Full Stack Developer', contact: 'Contact',
'site.description': 'Full Stack Developer specializing in React, Node.js, and modern web technologies', },
'hero.githubLink': 'GitHub Profile', site: {
'hero.linkedinLink': 'LinkedIn Profile', title: 'Joy Zhao - Full Stack Developer',
'footer.rights': 'All rights reserved', description: 'Full Stack Developer specializing in React, Node.js, and modern web technologies',
'project.tag.business': 'Business Project', },
'project.tag.opensource': 'Open Source', hero: {
'project.tag.personal': 'Personal Product', githubLink: 'GitHub Profile',
'project.tag.portfolio': 'Portfolio', linkedinLink: 'LinkedIn Profile',
'project.tag.ecommerce': 'E-Commerce', },
'project.visit': 'Visit', footer: {
'project.demo': 'Live Demo', rights: 'All rights reserved',
'projects.title': 'My Projects', },
'projects.description': 'A collection of my recent work, showcasing innovative solutions and clean code. Explore the details of each project below.' project: {
tag: {
business: 'Business Project',
opensource: 'Open Source',
personal: 'Personal Product',
portfolio: 'Portfolio',
ecommerce: 'E-Commerce',
},
visit: 'Visit',
demo: 'Live Demo',
},
projects: {
title: 'My Projects',
description: 'A collection of my recent work, showcasing innovative solutions and clean code. Explore the details of each project below.',
},
// Projects and services content has been inlined into respective page files // Projects and services content has been inlined into respective page files
// to reduce reliance on the translation system and improve maintainability // to reduce reliance on the translation system and improve maintainability
}, },
zh: { zh: {
'nav.home': '首页', nav: {
'nav.about': '关于', home: '首页',
'nav.services': '服务', about: '关于',
'nav.projects': '项目', services: '服务',
'nav.blog': '博客', projects: '项目',
'nav.contact': '联系', blog: '博客',
'site.title': 'Joy Zhao - 全栈开发者', contact: '联系',
'site.description': '专注于 React、Node.js 和现代 Web 技术的全栈开发者', },
'hero.githubLink': 'GitHub 主页', site: {
'hero.linkedinLink': 'LinkedIn 主页', title: 'Joy Zhao - 全栈开发者',
'footer.rights': '版权所有', description: '专注于 React、Node.js 和现代 Web 技术的全栈开发者',
'project.tag.business': '商业项目', },
'project.tag.opensource': '开源项目', hero: {
'project.tag.personal': '个人产品', githubLink: 'GitHub 主页',
'project.tag.portfolio': '作品集', linkedinLink: 'LinkedIn 主页',
'project.tag.ecommerce': '电子商务', },
'project.visit': '访问', footer: {
'project.demo': '在线演示', rights: '版权所有',
'projects.title': '我的项目', },
'projects.description': '这里展示了我最近的作品集,展现了创新解决方案和整洁的代码。请浏览下方了解每个项目的详细信息。' project: {
tag: {
business: '商业项目',
opensource: '开源项目',
personal: '个人产品',
portfolio: '作品集',
ecommerce: '电子商务',
},
visit: '访问',
demo: '在线演示',
},
projects: {
title: '我的项目',
description: '这里展示了我最近的作品集,展现了创新解决方案和整洁的代码。请浏览下方了解每个项目的详细信息。',
},
// Projects and services content has been inlined into respective page files // Projects and services content has been inlined into respective page files
// to reduce reliance on the translation system and improve maintainability // to reduce reliance on the translation system and improve maintainability
} },
} as const; } as const;
// 创建代理对象,保持与现有代码的兼容性
function createCompatibleUi() {
// 为每种语言创建代理
const compatibleUi: Record<string, any> = {};
Object.keys(structuredUi).forEach(lang => {
compatibleUi[lang] = new Proxy({}, {
get(target, prop) {
if (typeof prop !== 'string') return undefined;
// 处理点符号键 (如 'nav.home')
const parts = prop.split('.');
let value: any = structuredUi[lang as keyof typeof structuredUi];
// 遍历嵌套结构
for (const part of parts) {
if (value && typeof value === 'object' && part in value) {
value = value[part as keyof typeof value];
} else {
return undefined; // 键不存在
}
}
return value;
}
});
});
return compatibleUi;
}
// 导出兼容的UI对象
export const ui = createCompatibleUi() as typeof structuredUi;
// 导出结构化的UI对象供将来使用
export const structuredUI = structuredUi;

View File

@@ -2,14 +2,17 @@
import { ui, defaultLang, languages } from './ui'; import { ui, defaultLang, languages } from './ui';
export type Lang = keyof typeof languages; export type Lang = keyof typeof languages;
export type UiKeys = keyof typeof ui[typeof defaultLang];
// 简化类型定义,直接使用字符串类型
// 这样可以兼容点符号键,同时避免复杂的类型推导问题
export type UiKeys = string;
export function useTranslations(lang: Lang | undefined) { export function useTranslations(lang: Lang | undefined) {
const currentLang = lang || defaultLang; const currentLang = lang || defaultLang;
return function t(key: UiKeys, ...args: any[]): string { return function t(key: UiKeys, ...args: any[]): string {
let translation: string = ui[currentLang][key] || ui[defaultLang][key]; // 使用类型断言解决索引问题
let translation: string = (ui[currentLang] as any)[key] || (ui[defaultLang] as any)[key];
if (args.length > 0) { if (args.length > 0) {
args.forEach((arg, index) => { args.forEach((arg, index) => {
translation = translation.replace(`{${index}}`, arg); translation = translation.replace(`{${index}}`, arg);

View File

@@ -1,8 +1,202 @@
export const personalInfo = { export const personalInfo = {
name: "Joy Zhao", name: "Guiyang Zhao",
location: "Shanghai, China", location: "Shanghai, China",
avatar:"https://avatars.githubusercontent.com/u/24975063?v=4", avatar: "/images/avatar.jpg",
email: "zhaoguiyang18@gmail.com", email: "zhaoguiyang@gmail.com",
github: "https://github.com/zguiyang", github: "https://github.com/zhaoguiyang",
linkedin: "https://linkedin.com/in/zhaoguiyang" linkedin: "https://linkedin.com/in/zhaoguiyang",
};
export const projects = {
en: [
{
id: "taskify",
tag: "business",
title: "Taskify App",
icon: "📱",
color: "purple",
image: {
bg: "from-purple-500/20 to-purple-600/20",
hover: "from-purple-500/20 to-purple-600/20",
text: "text-purple-400",
},
description: [
"A comprehensive task management application with drag-and-drop functionality.",
"Built with React, TypeScript, and Tailwind CSS using modern development approaches.",
"Real-time collaboration through WebSocket integration for instant updates.",
"Advanced task filtering, sorting, and project management capabilities.",
],
tech: ["React", "Node.js", "MongoDB"],
link: "#",
},
{
id: "eshop",
tag: "ecommerce",
title: "E-Shop Platform",
icon: "🛒",
color: "green",
image: {
bg: "from-green-500/20 to-green-600/20",
hover: "from-green-500/20 to-green-600/20",
text: "text-green-400",
},
description: [
"Scalable e-commerce platform built with Next.js, Stripe payments, and TailwindCSS.",
"Mobile-first responsive design for optimal user experience across devices.",
"Integrated payment processing with Stripe for secure transactions.",
"Admin dashboard for inventory management and order processing.",
],
tech: ["Next.js", "Stripe", "TailwindCSS"],
link: "#",
},
{
id: "portfolio",
tag: "portfolio",
title: "Portfolio Site",
icon: "🌐",
color: "purple",
image: {
bg: "from-purple-500/20 to-purple-600/20",
hover: "from-purple-500/20 to-purple-600/20",
text: "text-purple-400",
},
description: [
"Personal portfolio showcasing my work, built with HTML, TailwindCSS, and Alpine.js.",
"Responsive design with dark mode support and smooth animations.",
"Optimized for performance with minimal JavaScript and efficient CSS.",
"Integrated blog section with markdown support for content management.",
],
tech: ["HTML", "TailwindCSS", "Alpine.js"],
link: "#",
},
],
zh: [
{
id: "taskify",
tag: "business",
title: "Taskify 应用",
icon: "📱",
color: "purple",
image: {
bg: "from-purple-500/20 to-purple-600/20",
hover: "from-purple-500/20 to-purple-600/20",
text: "text-purple-400",
},
description: [
"一个全面的任务管理应用,具有拖放功能。",
"使用React、TypeScript和Tailwind CSS构建采用现代开发方法。",
"通过WebSocket集成实现实时协作功能实现即时更新。",
"高级任务筛选、排序和项目管理功能。",
],
tech: ["React", "Node.js", "MongoDB"],
link: "#",
},
{
id: "eshop",
tag: "ecommerce",
title: "电商平台",
icon: "🛒",
color: "green",
image: {
bg: "from-green-500/20 to-green-600/20",
hover: "from-green-500/20 to-green-600/20",
text: "text-green-400",
},
description: [
"使用Next.js、Stripe支付和TailwindCSS构建的可扩展电子商务平台。",
"采用移动优先的响应式设计,提供最佳用户体验。",
"集成Stripe支付处理确保交易安全。",
"管理员仪表板,用于库存管理和订单处理。",
],
tech: ["Next.js", "Stripe", "TailwindCSS"],
link: "#",
},
{
id: "portfolio",
tag: "portfolio",
title: "个人作品集网站",
icon: "🌐",
color: "purple",
image: {
bg: "from-purple-500/20 to-purple-600/20",
hover: "from-purple-500/20 to-purple-600/20",
text: "text-purple-400",
},
description: [
"展示我的作品的个人作品集使用HTML、TailwindCSS和Alpine.js构建。",
"响应式设计,支持暗黑模式和平滑动画。",
"通过最小化JavaScript和高效CSS优化性能。",
"集成博客部分支持markdown内容管理。",
],
tech: ["HTML", "TailwindCSS", "Alpine.js"],
link: "#",
},
],
};
export const services = {
en: [
{
title: "Outsourcing Projects",
icon: {
svg: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>`,
gradient: "from-blue-500 to-cyan-500",
},
items: [
"Provide outsourcing project services for overseas clients",
"Project packaging, can provide services in different packages",
"Use popular technologies: React, nest.js, next.js, etc.",
"Project delivery, provide three months of free maintenance",
"Regardless of project size, can provide services",
],
color: "blue",
},
{
title: "Bug Fixes",
icon: {
svg: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>`,
gradient: "from-red-500 to-pink-500",
},
items: [
"Quickly locate and fix website issues, able to quickly locate and solve problems",
"Help you solve any problems encountered in the project, Bug, functional optimization issues",
"Application of Vue, React, Node and other technologies",
"Frontend automation, deployment, monitoring, automation, etc.",
"Only limited to Node.js projects",
],
color: "red",
},
],
zh: [
{
title: "外包项目",
icon: {
svg: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>`,
gradient: "from-blue-500 to-cyan-500",
},
items: [
"为海外客户提供外包项目服务",
"项目打包,可以提供不同套餐的服务",
"使用流行技术React、nest.js、next.js 等",
"项目交付,提供三个月免费维护",
"不论项目大小,都可以提供服务",
],
color: "blue",
},
{
title: "Bug 修复",
icon: {
svg: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>`,
gradient: "from-red-500 to-pink-500",
},
items: [
"快速定位和修复网站问题,能够快速定位和解决问题",
"帮助您解决项目中遇到的任何问题Bug、功能优化问题",
"Vue、React、Node 等技术的应用",
"前端自动化、部署、监控、自动化等",
"仅限于 Node.js 项目",
],
color: "red",
},
],
}; };

View File

@@ -5,7 +5,7 @@ import SkillsMarquee from "@/components/SkillsMarquee";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import { useTranslations, type Lang } from "@/i18n/utils"; import { useTranslations, type Lang } from "@/i18n/utils";
import { defaultLang } from "@/i18n/ui"; import { defaultLang } from "@/i18n/ui";
import { personalInfo } from "@/lib/data"; import { personalInfo, services } from "@/lib/data";
// 使用Astro.currentLocale获取当前语言环境 // 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang; const lang = Astro.currentLocale as Lang || defaultLang;
@@ -175,74 +175,26 @@ const pageTitle = t('site.title');
</h2> </h2>
<div class="grid md:grid-cols-2 gap-8"> <div class="grid md:grid-cols-2 gap-8">
<!-- Outsourcing Projects Card --> {services.en.map((service) => (
<div class="bg-white/70 dark:bg-slate-800/70 backdrop-blur-sm rounded-2xl p-8 border border-white/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105"> <div class="bg-white/70 dark:bg-slate-800/70 backdrop-blur-sm rounded-2xl p-8 border border-white/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
<div class="flex items-center mb-6"> <div class="flex items-center mb-6">
<div class="w-12 h-12 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-lg flex items-center justify-center mr-4"> <div class={`w-12 h-12 bg-gradient-to-r ${service.icon.gradient} rounded-lg flex items-center justify-center mr-4`}>
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path> <Fragment set:html={service.icon.svg} />
</svg> </svg>
</div>
<h3 class="text-2xl font-bold text-slate-800 dark:text-white">{service.title}</h3>
</div> </div>
<h3 class="text-2xl font-bold text-slate-800 dark:text-white">Outsourcing Projects</h3> <ul class="space-y-3">
{service.items.map((item, index) => (
<li class="flex items-start">
<span class={`text-${service.color}-500 mr-3 mt-1`}>{index + 1}.</span>
<span class="text-slate-600 dark:text-slate-300">{item}</span>
</li>
))}
</ul>
</div> </div>
<ul class="space-y-3"> ))}
<li class="flex items-start">
<span class="text-blue-500 mr-3 mt-1">1.</span>
<span class="text-slate-600 dark:text-slate-300">Provide outsourcing project services for overseas clients</span>
</li>
<li class="flex items-start">
<span class="text-blue-500 mr-3 mt-1">2.</span>
<span class="text-slate-600 dark:text-slate-300">Project packaging, can provide services in different packages</span>
</li>
<li class="flex items-start">
<span class="text-blue-500 mr-3 mt-1">3.</span>
<span class="text-slate-600 dark:text-slate-300">Use popular technologies: React, nest.js, next.js, etc.</span>
</li>
<li class="flex items-start">
<span class="text-blue-500 mr-3 mt-1">4.</span>
<span class="text-slate-600 dark:text-slate-300">Project delivery, provide three months of free maintenance</span>
</li>
<li class="flex items-start">
<span class="text-blue-500 mr-3 mt-1">5.</span>
<span class="text-slate-600 dark:text-slate-300">Regardless of project size, can provide services</span>
</li>
</ul>
</div>
<!-- Bug Fixes Card -->
<div class="bg-white/70 dark:bg-slate-800/70 backdrop-blur-sm rounded-2xl p-8 border border-white/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
<div class="flex items-center mb-6">
<div class="w-12 h-12 bg-gradient-to-r from-red-500 to-pink-500 rounded-lg flex items-center justify-center mr-4">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
</div>
<h3 class="text-2xl font-bold text-slate-800 dark:text-white">Bug Fixes</h3>
</div>
<ul class="space-y-3">
<li class="flex items-start">
<span class="text-red-500 mr-3 mt-1">1.</span>
<span class="text-slate-600 dark:text-slate-300">Quickly locate and fix website issues, able to quickly locate and solve problems</span>
</li>
<li class="flex items-start">
<span class="text-red-500 mr-3 mt-1">2.</span>
<span class="text-slate-600 dark:text-slate-300">Help you solve any problems encountered in the project, Bug, functional optimization issues</span>
</li>
<li class="flex items-start">
<span class="text-red-500 mr-3 mt-1">3.</span>
<span class="text-slate-600 dark:text-slate-300">Application of Vue, React, Node and other technologies</span>
</li>
<li class="flex items-start">
<span class="text-red-500 mr-3 mt-1">4.</span>
<span class="text-slate-600 dark:text-slate-300">Frontend automation, deployment, monitoring, automation, etc.</span>
</li>
<li class="flex items-start">
<span class="text-red-500 mr-3 mt-1">5.</span>
<span class="text-slate-600 dark:text-slate-300">Only limited to Node.js projects</span>
</li>
</ul>
</div>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -4,11 +4,15 @@ import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import { useTranslations, type Lang } from "@/i18n/utils"; import { useTranslations, type Lang } from "@/i18n/utils";
import { defaultLang } from "@/i18n/ui"; import { defaultLang } from "@/i18n/ui";
import { projects } from "@/lib/data";
// 使用Astro.currentLocale获取当前语言环境 // 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang; const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang); const t = useTranslations(lang);
const pageTitle = t('projects.title'); const pageTitle = t('projects.title');
// 根据当前语言获取项目数据
const currentProjects = projects[lang as keyof typeof projects] || projects.en;
--- ---
<Layout title={pageTitle}> <Layout title={pageTitle}>
@@ -41,194 +45,59 @@ const pageTitle = t('projects.title');
<div class="container max-w-6xl mx-auto px-6 md:px-4 relative z-10"> <div class="container max-w-6xl mx-auto px-6 md:px-4 relative z-10">
<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">
<!-- Taskify App Project --> {currentProjects.map((project) => (
<div class="group overflow-hidden border border-purple-500/20 hover:border-purple-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={`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 --> <!-- Project tag - positioned at top right -->
<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-${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 from-purple-500/20 to-purple-600/20 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-purple-500/10 to-purple-600/10 group-hover:from-purple-500/20 group-hover:to-purple-600/20 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 text-purple-400">
Taskify App
</div>
</div> </div>
</div>
<div class="p-6 pb-4"> <!-- Project image placeholder -->
<h3 class="text-xl group-hover:text-purple-400 transition-colors duration-300 flex items-center gap-2 font-bold mb-4"> <div class={`h-48 bg-gradient-to-br ${project.image.bg} relative overflow-hidden`}>
<span class="text-purple-500">📱</span> <div class={`absolute inset-0 bg-gradient-to-br ${project.image.bg} group-hover:${project.image.hover} transition-all duration-500`}></div>
Taskify App <div class="absolute bottom-4 left-4 right-4">
</h3> <div class={`bg-black/50 backdrop-blur-sm rounded px-3 py-1 text-xs font-mono ${project.image.text}`}>
</div> {project.title}
</div>
<div class="px-6 flex-grow">
<div class="space-y-3">
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>A comprehensive task management application with drag-and-drop functionality.</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>Built with React, TypeScript, and Tailwind CSS for modern development.</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>Real-time collaboration features with WebSocket integration for instant updates.</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>Advanced task filtering, sorting, and project management capabilities.</span>
</div> </div>
</div> </div>
<!-- Tech stack indicators with unified gray styling --> <div class="p-6 pb-4">
<div class="mt-6 mb-4 flex flex-wrap gap-2"> <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="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">React</span> <span class={`text-${project.color}-500`}>{project.icon}</span>
<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">Node.js</span> {project.title}
<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">MongoDB</span> </h3>
</div> </div>
</div>
<!-- Visit project link --> <div class="px-6 flex-grow">
<div class="p-6 pt-2"> <div class="space-y-3">
<a href="#" class="inline-flex items-center text-sm font-medium text-purple-500 hover:text-purple-600 dark:hover:text-purple-400 transition-colors duration-300"> {project.description.map((desc) => (
{t('project.visit')} <div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <span class={`text-${project.color}-500 mt-1`}>•</span>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path> <span>{desc}</span>
</svg> </div>
</a> ))}
</div>
</div>
<!-- E-Shop Platform Project -->
<div class="group overflow-hidden border border-purple-500/20 hover:border-purple-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-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.ecommerce')}</span>
</div>
<!-- Project image placeholder -->
<div class="h-48 bg-gradient-to-br from-green-500/20 to-green-600/20 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-green-500/10 to-green-600/10 group-hover:from-green-500/20 group-hover:to-green-600/20 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 text-green-400">
E-Shop Platform
</div> </div>
</div>
</div>
<div class="p-6 pb-4"> <!-- Tech stack indicators with unified gray styling -->
<h3 class="text-xl group-hover:text-green-400 transition-colors duration-300 flex items-center gap-2 font-bold mb-4"> <div class="mt-6 mb-4 flex flex-wrap gap-2">
<span class="text-green-500">🛒</span> {project.tech.map((tech) => (
E-Shop Platform <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>
</h3> ))}
</div>
<div class="px-6 flex-grow">
<div class="space-y-3">
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-green-500 mt-1">•</span>
<span>A scalable e-commerce platform with Next.js, Stripe payments, and TailwindCSS.</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-green-500 mt-1">•</span>
<span>Responsive design with mobile-first approach for optimal user experience.</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-green-500 mt-1">•</span>
<span>Integrated payment processing with Stripe for secure transactions.</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-green-500 mt-1">•</span>
<span>Admin dashboard for inventory management and order processing.</span>
</div> </div>
</div> </div>
<!-- Tech stack indicators with unified gray styling --> <!-- Visit project link -->
<div class="mt-6 mb-4 flex flex-wrap gap-2"> <div class="p-6 pt-2">
<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">Next.js</span> <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`}>
<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">Stripe</span> {t('project.visit')}
<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">TailwindCSS</span> <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>
))}
<!-- Visit project link -->
<div class="p-6 pt-2">
<a href="#" class="inline-flex items-center text-sm font-medium text-green-500 hover:text-green-600 dark:hover:text-green-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>
<!-- Portfolio Site Project -->
<div class="group overflow-hidden border border-purple-500/20 hover:border-purple-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-purple-100 dark:bg-purple-800 text-purple-800 dark:text-purple-200 text-xs rounded-full font-medium border border-purple-200 dark:border-purple-700">{t('project.tag.portfolio')}</span>
</div>
<!-- Project image placeholder -->
<div class="h-48 bg-gradient-to-br from-purple-500/20 to-purple-600/20 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-purple-500/10 to-purple-600/10 group-hover:from-purple-500/20 group-hover:to-purple-600/20 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 text-purple-400">
Portfolio Site
</div>
</div>
</div>
<div class="p-6 pb-4">
<h3 class="text-xl group-hover:text-purple-400 transition-colors duration-300 flex items-center gap-2 font-bold mb-4">
<span class="text-purple-500">🌐</span>
Portfolio Site
</h3>
</div>
<div class="px-6 flex-grow">
<div class="space-y-3">
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>My personal portfolio showcasing my work, built with HTML, TailwindCSS, and Alpine.js.</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>Responsive design with dark mode support and smooth animations.</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>Optimized for performance with minimal JavaScript and efficient CSS.</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>Integrated blog section with markdown support for content management.</span>
</div>
</div>
<!-- Tech stack indicators with unified gray styling -->
<div class="mt-6 mb-4 flex flex-wrap gap-2">
<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">HTML</span>
<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">TailwindCSS</span>
<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">Alpine.js</span>
</div>
</div>
<!-- Visit project link -->
<div class="p-6 pt-2">
<a href="#" class="inline-flex items-center text-sm font-medium text-purple-500 hover:text-purple-600 dark:hover:text-purple-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> </div>
</section> </section>

View File

@@ -1,11 +1,11 @@
--- ---
import Layout from "@/layouts/Layout.astro"; import Layout from "@/layouts/Layout.astro";
import GlassHeader from "@/components/GlassHeader.tsx"; import GlassHeader from "@/components/GlassHeader";
import SkillsMarquee from "@/components/SkillsMarquee.tsx"; import SkillsMarquee from "@/components/SkillsMarquee";
import Footer from "@/components/Footer.tsx"; import Footer from "@/components/Footer";
import { useTranslations, type Lang } from "@/i18n/utils"; import { useTranslations, type Lang } from "@/i18n/utils";
import { defaultLang } from "@/i18n/ui"; import { defaultLang } from "@/i18n/ui";
import { personalInfo } from "@/lib/data"; import { personalInfo, services } from "@/lib/data";
// 使用Astro.currentLocale获取当前语言环境 // 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang; const lang = Astro.currentLocale as Lang || defaultLang;
@@ -175,74 +175,26 @@ const pageTitle = t('site.title');
</h2> </h2>
<div class="grid md:grid-cols-2 gap-8"> <div class="grid md:grid-cols-2 gap-8">
<!-- Outsourcing Projects Card --> {services.zh.map((service) => (
<div class="bg-white/70 dark:bg-slate-800/70 backdrop-blur-sm rounded-2xl p-8 border border-white/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105"> <div class="bg-white/70 dark:bg-slate-800/70 backdrop-blur-sm rounded-2xl p-8 border border-white/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
<div class="flex items-center mb-6"> <div class="flex items-center mb-6">
<div class="w-12 h-12 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-lg flex items-center justify-center mr-4"> <div class={`w-12 h-12 bg-gradient-to-r ${service.icon.gradient} rounded-lg flex items-center justify-center mr-4`}>
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path> <Fragment set:html={service.icon.svg} />
</svg> </svg>
</div>
<h3 class="text-2xl font-bold text-slate-800 dark:text-white">{service.title}</h3>
</div> </div>
<h3 class="text-2xl font-bold text-slate-800 dark:text-white">外包项目</h3> <ul class="space-y-3">
{service.items.map((item, index) => (
<li class="flex items-start">
<span class={`text-${service.color}-500 mr-3 mt-1`}>{index + 1}.</span>
<span class="text-slate-600 dark:text-slate-300">{item}</span>
</li>
))}
</ul>
</div> </div>
<ul class="space-y-3"> ))}
<li class="flex items-start">
<span class="text-blue-500 mr-3 mt-1">1.</span>
<span class="text-slate-600 dark:text-slate-300">为海外客户提供外包项目服务</span>
</li>
<li class="flex items-start">
<span class="text-blue-500 mr-3 mt-1">2.</span>
<span class="text-slate-600 dark:text-slate-300">项目打包,可以提供不同套餐的服务</span>
</li>
<li class="flex items-start">
<span class="text-blue-500 mr-3 mt-1">3.</span>
<span class="text-slate-600 dark:text-slate-300">使用流行技术React、nest.js、next.js 等</span>
</li>
<li class="flex items-start">
<span class="text-blue-500 mr-3 mt-1">4.</span>
<span class="text-slate-600 dark:text-slate-300">项目交付,提供三个月免费维护</span>
</li>
<li class="flex items-start">
<span class="text-blue-500 mr-3 mt-1">5.</span>
<span class="text-slate-600 dark:text-slate-300">不论项目大小,都可以提供服务</span>
</li>
</ul>
</div>
<!-- Bug Fixes Card -->
<div class="bg-white/70 dark:bg-slate-800/70 backdrop-blur-sm rounded-2xl p-8 border border-white/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
<div class="flex items-center mb-6">
<div class="w-12 h-12 bg-gradient-to-r from-red-500 to-pink-500 rounded-lg flex items-center justify-center mr-4">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
</div>
<h3 class="text-2xl font-bold text-slate-800 dark:text-white">Bug 修复</h3>
</div>
<ul class="space-y-3">
<li class="flex items-start">
<span class="text-red-500 mr-3 mt-1">1.</span>
<span class="text-slate-600 dark:text-slate-300">快速定位和修复网站问题,能够快速定位和解决问题</span>
</li>
<li class="flex items-start">
<span class="text-red-500 mr-3 mt-1">2.</span>
<span class="text-slate-600 dark:text-slate-300">帮助您解决项目中遇到的任何问题Bug、功能优化问题</span>
</li>
<li class="flex items-start">
<span class="text-red-500 mr-3 mt-1">3.</span>
<span class="text-slate-600 dark:text-slate-300">Vue、React、Node 等技术的应用</span>
</li>
<li class="flex items-start">
<span class="text-red-500 mr-3 mt-1">4.</span>
<span class="text-slate-600 dark:text-slate-300">前端自动化、部署、监控、自动化等</span>
</li>
<li class="flex items-start">
<span class="text-red-500 mr-3 mt-1">5.</span>
<span class="text-slate-600 dark:text-slate-300">仅限于 Node.js 项目</span>
</li>
</ul>
</div>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -4,11 +4,15 @@ import GlassHeader from "@/components/GlassHeader";
import Footer from "@/components/Footer"; import Footer from "@/components/Footer";
import { useTranslations, type Lang } from "@/i18n/utils"; import { useTranslations, type Lang } from "@/i18n/utils";
import { defaultLang } from "@/i18n/ui"; import { defaultLang } from "@/i18n/ui";
import { projects } from "@/lib/data";
// 使用Astro.currentLocale获取当前语言环境 // 使用Astro.currentLocale获取当前语言环境
const lang = Astro.currentLocale as Lang || defaultLang; const lang = Astro.currentLocale as Lang || defaultLang;
const t = useTranslations(lang); const t = useTranslations(lang);
const pageTitle = t('projects.title'); const pageTitle = t('projects.title');
// 根据当前语言获取项目数据
const currentProjects = projects[lang as keyof typeof projects] || projects.en;
--- ---
<Layout title={pageTitle}> <Layout title={pageTitle}>
@@ -41,194 +45,59 @@ const pageTitle = t('projects.title');
<div class="container max-w-6xl mx-auto px-6 md:px-4 relative z-10"> <div class="container max-w-6xl mx-auto px-6 md:px-4 relative z-10">
<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">
<!-- Taskify App Project --> {currentProjects.map((project) => (
<div class="group overflow-hidden border border-purple-500/20 hover:border-purple-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={`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 --> <!-- Project tag - positioned at top right -->
<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-${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 from-purple-500/20 to-purple-600/20 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-purple-500/10 to-purple-600/10 group-hover:from-purple-500/20 group-hover:to-purple-600/20 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 text-purple-400">
Taskify 应用
</div>
</div> </div>
</div>
<div class="p-6 pb-4"> <!-- Project image placeholder -->
<h3 class="text-xl group-hover:text-purple-400 transition-colors duration-300 flex items-center gap-2 font-bold mb-4"> <div class={`h-48 bg-gradient-to-br ${project.image.bg} relative overflow-hidden`}>
<span class="text-purple-500">📱</span> <div class={`absolute inset-0 bg-gradient-to-br ${project.image.bg} group-hover:${project.image.hover} transition-all duration-500`}></div>
Taskify 应用 <div class="absolute bottom-4 left-4 right-4">
</h3> <div class={`bg-black/50 backdrop-blur-sm rounded px-3 py-1 text-xs font-mono ${project.image.text}`}>
</div> {project.title}
</div>
<div class="px-6 flex-grow">
<div class="space-y-3">
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>一个全面的任务管理应用,具有拖放功能。</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>使用React、TypeScript和Tailwind CSS构建采用现代开发方法。</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>通过WebSocket集成实现实时协作功能实现即时更新。</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>高级任务筛选、排序和项目管理功能。</span>
</div> </div>
</div> </div>
<!-- Tech stack indicators with unified gray styling --> <div class="p-6 pb-4">
<div class="mt-6 mb-4 flex flex-wrap gap-2"> <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="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">React</span> <span class={`text-${project.color}-500`}>{project.icon}</span>
<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">Node.js</span> {project.title}
<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">MongoDB</span> </h3>
</div> </div>
</div>
<!-- Visit project link --> <div class="px-6 flex-grow">
<div class="p-6 pt-2"> <div class="space-y-3">
<a href="#" class="inline-flex items-center text-sm font-medium text-purple-500 hover:text-purple-600 dark:hover:text-purple-400 transition-colors duration-300"> {project.description.map((desc) => (
{t('project.visit')} <div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <span class={`text-${project.color}-500 mt-1`}>•</span>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path> <span>{desc}</span>
</svg> </div>
</a> ))}
</div>
</div>
<!-- E-Shop Platform Project -->
<div class="group overflow-hidden border border-purple-500/20 hover:border-purple-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-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.ecommerce')}</span>
</div>
<!-- Project image placeholder -->
<div class="h-48 bg-gradient-to-br from-green-500/20 to-green-600/20 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-green-500/10 to-green-600/10 group-hover:from-green-500/20 group-hover:to-green-600/20 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 text-green-400">
电商平台
</div> </div>
</div>
</div>
<div class="p-6 pb-4"> <!-- Tech stack indicators with unified gray styling -->
<h3 class="text-xl group-hover:text-green-400 transition-colors duration-300 flex items-center gap-2 font-bold mb-4"> <div class="mt-6 mb-4 flex flex-wrap gap-2">
<span class="text-green-500">🛒</span> {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>
</h3> ))}
</div>
<div class="px-6 flex-grow">
<div class="space-y-3">
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-green-500 mt-1">•</span>
<span>使用Next.js、Stripe支付和TailwindCSS构建的可扩展电子商务平台。</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-green-500 mt-1">•</span>
<span>采用移动优先的响应式设计,提供最佳用户体验。</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-green-500 mt-1">•</span>
<span>集成Stripe支付处理确保交易安全。</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-green-500 mt-1">•</span>
<span>管理员仪表板,用于库存管理和订单处理。</span>
</div> </div>
</div> </div>
<!-- Tech stack indicators with unified gray styling --> <!-- Visit project link -->
<div class="mt-6 mb-4 flex flex-wrap gap-2"> <div class="p-6 pt-2">
<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">Next.js</span> <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`}>
<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">Stripe</span> {t('project.visit')}
<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">TailwindCSS</span> <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>
))}
<!-- Visit project link -->
<div class="p-6 pt-2">
<a href="#" class="inline-flex items-center text-sm font-medium text-green-500 hover:text-green-600 dark:hover:text-green-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>
<!-- Portfolio Site Project -->
<div class="group overflow-hidden border border-purple-500/20 hover:border-purple-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-purple-100 dark:bg-purple-800 text-purple-800 dark:text-purple-200 text-xs rounded-full font-medium border border-purple-200 dark:border-purple-700">{t('project.tag.portfolio')}</span>
</div>
<!-- Project image placeholder -->
<div class="h-48 bg-gradient-to-br from-purple-500/20 to-purple-600/20 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-purple-500/10 to-purple-600/10 group-hover:from-purple-500/20 group-hover:to-purple-600/20 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 text-purple-400">
个人作品集网站
</div>
</div>
</div>
<div class="p-6 pb-4">
<h3 class="text-xl group-hover:text-purple-400 transition-colors duration-300 flex items-center gap-2 font-bold mb-4">
<span class="text-purple-500">🌐</span>
个人作品集网站
</h3>
</div>
<div class="px-6 flex-grow">
<div class="space-y-3">
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>展示我的作品的个人作品集使用HTML、TailwindCSS和Alpine.js构建。</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>响应式设计,支持暗黑模式和平滑动画。</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>通过最小化JavaScript和高效CSS优化性能。</span>
</div>
<div class="flex items-start gap-2 text-sm text-muted-foreground group-hover:text-foreground transition-colors duration-300">
<span class="text-purple-500 mt-1">•</span>
<span>集成博客部分支持markdown内容管理。</span>
</div>
</div>
<!-- Tech stack indicators with unified gray styling -->
<div class="mt-6 mb-4 flex flex-wrap gap-2">
<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">HTML</span>
<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">TailwindCSS</span>
<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">Alpine.js</span>
</div>
</div>
<!-- Visit project link -->
<div class="p-6 pt-2">
<a href="#" class="inline-flex items-center text-sm font-medium text-purple-500 hover:text-purple-600 dark:hover:text-purple-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> </div>
</section> </section>