feat(i18n): implement internationalization support for website

Add i18next with Chinese and English language support across all website sections. Convert hardcoded strings to translation keys in about, cases, services, team, footer, hero, and navbar components. Update document title and meta description to use translated values. Add language toggle in navbar. Remove contact section and unused UI components (button, card, input, textarea).

BREAKING CHANGE: Site content now requires translation files to render properly. Removed @radix-ui/react-slot and class-variance-authority dependencies as contact form UI components were deleted.
This commit is contained in:
lafay
2026-04-28 12:02:55 +08:00
parent 4a7bf5a4a3
commit 99e26f3a2f
19 changed files with 803 additions and 750 deletions

156
package-lock.json generated
View File

@@ -8,13 +8,14 @@
"name": "qczl-website",
"version": "0.0.0",
"dependencies": {
"@radix-ui/react-slot": "^1.2.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"geist": "^1.7.0",
"i18next": "^26.0.8",
"i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^0.503.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-i18next": "^17.0.6",
"tailwind-merge": "^3.2.0"
},
"devDependencies": {
@@ -261,6 +262,15 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.29.2",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz",
@@ -1487,39 +1497,6 @@
"node": ">= 10"
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.2.4",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
"integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27",
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
@@ -2226,7 +2203,7 @@
"version": "19.2.14",
"resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -2329,18 +2306,6 @@
],
"license": "CC-BY-4.0"
},
"node_modules/class-variance-authority": {
"version": "0.7.1",
"resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
"license": "Apache-2.0",
"dependencies": {
"clsx": "^2.1.1"
},
"funding": {
"url": "https://polar.sh/cva"
}
},
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz",
@@ -2368,7 +2333,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"node_modules/debug": {
@@ -2531,6 +2496,52 @@
"dev": true,
"license": "ISC"
},
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"license": "MIT",
"dependencies": {
"void-elements": "3.1.0"
}
},
"node_modules/i18next": {
"version": "26.0.8",
"resolved": "https://registry.npmmirror.com/i18next/-/i18next-26.0.8.tgz",
"integrity": "sha512-BRzLom0mhDhV9v0QhgUUHWQJuwFmnr1194xEcNLYD6ym8y8s542n4jXUvRLnhNTbh9PmpU6kGZamyuGHQMsGjw==",
"funding": [
{
"type": "individual",
"url": "https://www.locize.com/i18next"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
},
{
"type": "individual",
"url": "https://www.locize.com"
}
],
"license": "MIT",
"peerDependencies": {
"typescript": "^5 || ^6"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/i18next-browser-languagedetector": {
"version": "8.2.1",
"resolved": "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz",
"integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz",
@@ -3048,6 +3059,33 @@
"react": "^19.2.5"
}
},
"node_modules/react-i18next": {
"version": "17.0.6",
"resolved": "https://registry.npmmirror.com/react-i18next/-/react-i18next-17.0.6.tgz",
"integrity": "sha512-WzJ6SMKF+GTD7JZZqxSR1AKKmXjaSu39sClUrNlwxS4Tl7a99O+ltFy6yhPMO+wgZuxpQjJ2PZkfrQKmAqrLhw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
"html-parse-stringify": "^3.0.1",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"i18next": ">= 26.0.1",
"react": ">= 16.8.0",
"typescript": "^5 || ^6"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz",
@@ -3270,7 +3308,7 @@
"version": "6.0.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -3311,6 +3349,15 @@
"browserslist": ">= 4.21.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/vite": {
"version": "6.4.2",
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.2.tgz",
@@ -3386,6 +3433,15 @@
}
}
},
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",

View File

@@ -9,13 +9,14 @@
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-slot": "^1.2.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"geist": "^1.7.0",
"i18next": "^26.0.8",
"i18next-browser-languagedetector": "^8.2.1",
"lucide-react": "^0.503.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-i18next": "^17.0.6",
"tailwind-merge": "^3.2.0"
},
"devDependencies": {

View File

@@ -1,3 +1,5 @@
import { useEffect } from "react"
import { useTranslation } from "react-i18next"
import { Navbar } from "./components/sections/navbar"
import { Hero } from "./components/sections/hero"
import { About } from "./components/sections/about"
@@ -6,6 +8,15 @@ import { Team } from "./components/sections/team"
import { Footer } from "./components/sections/footer"
function App() {
const { t, i18n } = useTranslation()
useEffect(() => {
document.title = `${t("site.title")} - ${t("site.subtitle")}`
document
.querySelector('meta[name="description"]')
?.setAttribute("content", `${t("site.title")} - ${t("site.subtitle")}`)
}, [t, i18n.language])
return (
<div className="min-h-screen bg-background text-foreground">
<Navbar />

View File

@@ -1,53 +1,42 @@
import { useTranslation } from "react-i18next"
import { Zap, Shield, Code2, Rocket, Eye, Heart, Bot, Gauge, Layers, MessageSquare } from "lucide-react"
const stats = [
{ label: "projects shipped", value: "100+", desc: "交付项目" },
{ label: "years building", value: "5+", desc: "持续开发" },
{ label: "client retention", value: "95%", desc: "客户留存" },
]
const advantages = [
{ icon: Bot, label: "AI 驱动", desc: "深度整合 AI 工具链,开发效率提升数倍" },
{ icon: Gauge, label: "快速交付", desc: "两周一个迭代,持续交付可运行的成果" },
{ icon: Layers, label: "全栈覆盖", desc: "前端、后端、移动端、基础设施一个团队搞定" },
{ icon: MessageSquare, label: "直接沟通", desc: "你直接和写代码的人对话,需求零损耗" },
]
const principles = [
{ icon: Code2, title: "Clean Code", desc: "代码即文档。我们写人类可读的代码。" },
{ icon: Zap, title: "Ship Fast", desc: "两周一个迭代,快速验证想法。" },
{ icon: Shield, title: "Own It", desc: "全栈负责到底。从需求到部署。" },
{ icon: Eye, title: "Transparency", desc: "进度完全透明,随时可查看代码和部署。" },
{ icon: Heart, title: "Craftsmanship", desc: "把每个项目当作自己的作品。" },
{ icon: Rocket, title: "Scale Ready", desc: "架构为增长设计,今天的小项目明天的大平台。" },
]
const principleIcons = [Code2, Zap, Shield, Eye, Heart, Rocket] as const
const advantageIcons = [Bot, Gauge, Layers, MessageSquare] as const
export function About() {
const { t } = useTranslation()
type StatItem = { value: string; label: string; desc: string }
type AdvantageItem = { title: string; desc: string }
type PrincipleItem = { title: string; desc: string }
const stats = t("about.stats", { returnObjects: true }) as StatItem[]
const advantages = t("about.whyUs.items", { returnObjects: true }) as AdvantageItem[]
const principles = t("about.principles.items", { returnObjects: true }) as PrincipleItem[]
return (
<section id="about" className="py-32 relative">
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-accent/[0.02] to-transparent" />
<div className="max-w-6xl mx-auto px-6 relative">
{/* Section header */}
<div className="mb-20">
<div className="flex items-center gap-3 mb-6">
<div className="h-px w-8 bg-accent/50" />
<span className="text-[11px] text-accent font-medium tracking-widest uppercase">about us</span>
<span className="text-[11px] text-accent font-medium tracking-widest uppercase">{t("about.label")}</span>
</div>
<h2 className="text-4xl sm:text-5xl font-bold text-foreground mb-6 tracking-tight">
{t("about.title")}
</h2>
<p className="text-base text-muted-foreground leading-relaxed max-w-2xl">
{t("about.description")}
</p>
</div>
{/* Stats row */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-24">
{stats.map((stat) => (
<div
key={stat.label}
{Array.isArray(stats) && stats.map((stat) => (
<div
key={stat.label}
className="group p-8 rounded-xl bg-card/50 border border-border/50 hover:border-accent/20 transition-all duration-300"
>
<div className="text-4xl font-bold text-foreground mb-2 group-hover:text-accent transition-colors">
@@ -59,48 +48,52 @@ export function About() {
))}
</div>
{/* Advantages */}
<div className="mb-20">
<div className="flex items-center gap-3 mb-10">
<div className="h-px w-8 bg-accent/50" />
<span className="text-[11px] text-accent font-medium tracking-widest uppercase">why us</span>
<span className="text-[11px] text-accent font-medium tracking-widest uppercase">{t("about.whyUs.label")}</span>
</div>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-x-8 gap-y-10">
{advantages.map((item) => (
<div key={item.label} className="group">
<div className="w-10 h-10 rounded-lg bg-accent/[0.07] border border-accent/15 flex items-center justify-center mb-4 group-hover:bg-accent/15 group-hover:border-accent/30 transition-all duration-300">
<item.icon className="w-4 h-4 text-accent" />
{Array.isArray(advantages) && advantages.map((item, index) => {
const Icon = advantageIcons[index]
return (
<div key={item.title} className="group">
<div className="w-10 h-10 rounded-lg bg-accent/[0.07] border border-accent/15 flex items-center justify-center mb-4 group-hover:bg-accent/15 group-hover:border-accent/30 transition-all duration-300">
<Icon className="w-4 h-4 text-accent" />
</div>
<h3 className="text-sm font-semibold text-foreground mb-1.5 tracking-tight">{item.title}</h3>
<p className="text-xs text-muted-foreground leading-relaxed">{item.desc}</p>
</div>
<h3 className="text-sm font-semibold text-foreground mb-1.5 tracking-tight">{item.label}</h3>
<p className="text-xs text-muted-foreground leading-relaxed">{item.desc}</p>
</div>
))}
)
})}
</div>
</div>
{/* Principles */}
<div>
<div className="flex items-center gap-3 mb-8">
<div className="h-px w-8 bg-accent/50" />
<span className="text-[11px] text-accent font-medium tracking-widest uppercase">our principles</span>
<span className="text-[11px] text-accent font-medium tracking-widest uppercase">{t("about.principles.label")}</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-12">
{principles.map((item, index) => (
<div
key={item.title}
className="group flex items-center gap-4 py-4 border-b border-border/10"
>
<span className="text-xs font-mono text-accent/25 group-hover:text-accent/50 transition-colors w-5 shrink-0 text-right">
{String(index + 1).padStart(2, "0")}
</span>
<item.icon className="w-3.5 h-3.5 text-accent/60 shrink-0" />
<div className="flex items-baseline gap-2 min-w-0">
<h3 className="text-sm font-semibold text-foreground shrink-0">{item.title}</h3>
<div className="h-px flex-1 bg-border/20 min-w-4" />
<p className="text-xs text-muted-foreground shrink-0">{item.desc}</p>
{Array.isArray(principles) && principles.map((item, index) => {
const Icon = principleIcons[index]
return (
<div
key={item.title}
className="group flex items-center gap-4 py-4 border-b border-border/10"
>
<span className="text-xs font-mono text-accent/25 group-hover:text-accent/50 transition-colors w-5 shrink-0 text-right">
{String(index + 1).padStart(2, "0")}
</span>
<Icon className="w-3.5 h-3.5 text-accent/60 shrink-0" />
<div className="flex items-baseline gap-2 min-w-0">
<h3 className="text-sm font-semibold text-foreground shrink-0">{item.title}</h3>
<div className="h-px flex-1 bg-border/20 min-w-4" />
<p className="text-xs text-muted-foreground shrink-0">{item.desc}</p>
</div>
</div>
</div>
))}
)
})}
</div>
</div>
</div>

View File

@@ -1,83 +1,60 @@
import { Smartphone, Building2, Printer } from "lucide-react"
import { useTranslation } from "react-i18next"
import { Smartphone, Building2, Printer, LucideIcon } from "lucide-react"
const cases = [
{
id: "01",
title: "威友",
subtitle: "Weiyou",
category: "校园社交",
description: "面向高校学生的校园交友 APP。支持兴趣匹配、活动发布、匿名树洞等功能覆盖 iOS、Android 双端及 Web 管理后台。",
platforms: ["iOS", "Android", "Web"],
tags: ["React Native", "Go", "PostgreSQL"],
icon: Smartphone,
status: "live",
},
{
id: "02",
title: "CarrotERP",
subtitle: "Enterprise Resource Planning",
category: "企业管理",
description: "内部使用的 ERP 管理软件,对标行业领军产品。涵盖采购、库存、财务、人事等核心模块,支撑企业日常运营全流程。",
platforms: ["Web", "Desktop"],
tags: ["React", "Go", "PostgreSQL"],
icon: Building2,
status: "live",
},
{
id: "03",
title: "CarrotPrint",
subtitle: "Self-service Printing",
category: "自助服务",
description: "自助打印解决方案覆盖微信小程序、Windows 客户端、Linux 服务端三端。用户扫码上传文件,就近打印取件。",
platforms: ["小程序", "Windows", "Linux"],
tags: ["Rust", "C#", "Taro", "Go"],
icon: Printer,
status: "live",
},
]
const caseIcons: Record<string, LucideIcon> = {
"威友": Smartphone,
"CarrotERP": Building2,
"CarrotPrint": Printer,
}
export function Cases() {
const { t } = useTranslation()
type CaseItem = {
id: string
title: string
subtitle: string
category: string
description: string
platforms: string[]
tags: string[]
status: string
}
const items = t("cases.items", { returnObjects: true }) as CaseItem[]
return (
<section id="cases" className="py-32 relative">
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-accent/[0.015] to-transparent" />
<div className="max-w-6xl mx-auto px-6 relative">
{/* Section header */}
<div className="mb-20">
<div className="flex items-center gap-3 mb-6">
<div className="h-px w-8 bg-accent/50" />
<span className="text-[11px] text-accent font-medium tracking-widest uppercase">selected work</span>
<span className="text-[11px] text-accent font-medium tracking-widest uppercase">{t("cases.label")}</span>
</div>
<h2 className="text-4xl sm:text-5xl font-bold text-foreground mb-6 tracking-tight">
{t("cases.title")}
</h2>
<p className="text-base text-muted-foreground leading-relaxed max-w-2xl">
{t("cases.description")}
</p>
</div>
{/* Cases list - one column, alternating layout */}
<div className="space-y-24">
{cases.map((item, index) => {
{Array.isArray(items) && items.map((item, index) => {
const isEven = index % 2 === 0
const Icon = item.icon
const Icon = caseIcons[item.title] || Smartphone
return (
<div
key={item.title}
className="group"
>
<div key={item.title} className="group">
<div className={`grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12 items-center ${isEven ? '' : 'lg:direction-rtl'}`}>
{/* Left side - Visual */}
<div className={`lg:col-span-5 ${isEven ? 'lg:order-1' : 'lg:order-2'}`}>
<div className="relative">
{/* Card mockup */}
<div className="rounded-2xl bg-card/40 border border-border/40 p-8 relative overflow-hidden">
{/* Background glow */}
<div className="absolute top-0 right-0 w-48 h-48 bg-accent/5 rounded-full blur-[60px]" />
<div className="relative">
{/* Icon & Title */}
<div className="flex items-center gap-4 mb-8">
<div className="w-14 h-14 rounded-xl bg-gradient-to-br from-accent/20 to-accent/5 border border-accent/20 flex items-center justify-center">
<Icon className="w-6 h-6 text-accent" />
@@ -88,9 +65,8 @@ export function Cases() {
</div>
</div>
{/* Platform badges */}
<div className="flex flex-wrap gap-2 mb-6">
{item.platforms.map((platform) => (
{item.platforms.map((platform: string) => (
<span
key={platform}
className="px-3 py-1.5 text-[11px] text-accent bg-accent/10 border border-accent/20 rounded-md font-medium"
@@ -100,7 +76,6 @@ export function Cases() {
))}
</div>
{/* Status */}
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-accent animate-pulse" />
<span className="text-[11px] text-accent font-medium">{item.status}</span>
@@ -108,36 +83,30 @@ export function Cases() {
</div>
</div>
{/* Index number */}
<div className={`absolute -top-6 ${isEven ? '-left-4' : '-right-4'} text-7xl font-bold text-foreground/[0.03] select-none`}>
{item.id}
</div>
</div>
</div>
{/* Right side - Details */}
<div className={`lg:col-span-7 ${isEven ? 'lg:order-2' : 'lg:order-1'}`}>
<div>
{/* Category */}
<span className="text-[11px] text-accent font-medium tracking-widest uppercase mb-4 block">
{item.category}
</span>
{/* Title */}
<h3 className="text-3xl sm:text-4xl font-bold text-foreground mb-4 tracking-tight">
{item.title}
</h3>
{/* Description */}
<p className="text-sm text-muted-foreground leading-relaxed mb-8 max-w-lg">
{item.description}
</p>
{/* Tech stack */}
<div>
<span className="text-[10px] text-muted-foreground uppercase tracking-widest mb-3 block">tech stack</span>
<span className="text-[10px] text-muted-foreground uppercase tracking-widest mb-3 block">{t("cases.techStack")}</span>
<div className="flex flex-wrap gap-2">
{item.tags.map((tag) => (
{item.tags.map((tag: string) => (
<span
key={tag}
className="px-3 py-1.5 text-[11px] text-muted-foreground bg-secondary/50 border border-border/40 rounded-md"

View File

@@ -1,162 +0,0 @@
import { useState } from "react"
import { Mail, MapPin, Phone, Send } from "lucide-react"
import { Button } from "../ui/button"
import { Input } from "../ui/input"
import { Textarea } from "../ui/textarea"
import { Card, CardContent } from "../ui/card"
const contactInfo = [
{
icon: MapPin,
title: "公司地址",
content: "山东省威海市",
},
{
icon: Phone,
title: "联系电话",
content: "400-XXX-XXXX",
},
{
icon: Mail,
title: "电子邮箱",
content: "contact@qczl-tech.com",
},
]
export function Contact() {
const [formData, setFormData] = useState({
name: "",
email: "",
company: "",
message: "",
})
const [submitted, setSubmitted] = useState(false)
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
setSubmitted(true)
setTimeout(() => {
setSubmitted(false)
setFormData({ name: "", email: "", company: "", message: "" })
}, 3000)
}
return (
<section id="contact" className="py-24 bg-secondary/30">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-16">
<h2 className="text-3xl sm:text-4xl font-bold text-foreground mb-4">
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-1 space-y-4">
{contactInfo.map((info) => (
<Card key={info.title}>
<CardContent className="flex items-start gap-4 p-6">
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
<info.icon className="w-5 h-5 text-primary" />
</div>
<div>
<h3 className="font-semibold text-foreground mb-1">
{info.title}
</h3>
<p className="text-sm text-muted-foreground">
{info.content}
</p>
</div>
</CardContent>
</Card>
))}
</div>
<div className="lg:col-span-2">
<Card>
<CardContent className="p-6 sm:p-8">
{submitted ? (
<div className="text-center py-12">
<div className="w-16 h-16 rounded-full bg-green-100 flex items-center justify-center mx-auto mb-4">
<Send className="w-8 h-8 text-green-600" />
</div>
<h3 className="text-xl font-semibold text-foreground mb-2">
</h3>
<p className="text-muted-foreground">
</p>
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
*
</label>
<Input
placeholder="请输入您的姓名"
value={formData.name}
onChange={(e) =>
setFormData({ ...formData, name: e.target.value })
}
required
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
*
</label>
<Input
type="email"
placeholder="请输入您的邮箱"
value={formData.email}
onChange={(e) =>
setFormData({ ...formData, email: e.target.value })
}
required
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
</label>
<Input
placeholder="请输入您的公司名称"
value={formData.company}
onChange={(e) =>
setFormData({ ...formData, company: e.target.value })
}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
*
</label>
<Textarea
placeholder="请描述您的需求或问题"
rows={5}
value={formData.message}
onChange={(e) =>
setFormData({ ...formData, message: e.target.value })
}
required
/>
</div>
<Button type="submit" size="lg" className="w-full sm:w-auto gap-2">
<Send className="w-4 h-4" />
</Button>
</form>
)}
</CardContent>
</Card>
</div>
</div>
</div>
</section>
)
}

View File

@@ -1,68 +1,68 @@
import { useTranslation } from "react-i18next"
import { Terminal, ArrowUpRight, Mail } from "lucide-react"
export function Footer() {
const { t } = useTranslation()
return (
<footer className="py-32 relative">
<div className="absolute inset-0 bg-gradient-to-t from-accent/[0.03] to-transparent" />
<div className="max-w-6xl mx-auto px-6 relative">
{/* CTA Section */}
<div className="mb-20 p-10 md:p-14 rounded-2xl bg-card/30 border border-border/30 relative overflow-hidden">
{/* Background glow */}
<div className="absolute top-0 right-0 w-64 h-64 bg-accent/5 rounded-full blur-[80px]" />
<div className="relative flex flex-col lg:flex-row lg:items-center lg:justify-between gap-8">
<div>
<div className="flex items-center gap-3 mb-4">
<div className="h-px w-8 bg-accent/50" />
<span className="text-[11px] text-accent font-medium tracking-widest uppercase">get in touch</span>
<span className="text-[11px] text-accent font-medium tracking-widest uppercase">{t("footer.label")}</span>
</div>
<h3 className="text-3xl sm:text-4xl font-bold text-foreground mb-4 tracking-tight">
{t("footer.title")}
</h3>
<p className="text-sm text-muted-foreground leading-relaxed max-w-md">
24
{t("footer.description")}
</p>
</div>
<div className="flex flex-col sm:flex-row gap-3 shrink-0">
<a
href="mailto:system@qczlit.cn"
href={`mailto:${t("footer.email")}`}
className="group inline-flex items-center justify-center gap-2 text-xs font-medium text-background bg-foreground px-6 py-3 rounded-lg hover:bg-foreground/90 transition-all"
>
<Mail className="w-3.5 h-3.5" />
system@qczlit.cn
{t("footer.email")}
<ArrowUpRight className="w-3.5 h-3.5 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
</a>
</div>
</div>
</div>
{/* Footer bottom */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-6 pt-10 border-t border-border/30">
<div className="flex items-center gap-2.5">
<div className="w-7 h-7 rounded-md bg-accent/10 border border-accent/20 flex items-center justify-center">
<Terminal className="w-3.5 h-3.5 text-accent" />
</div>
<div>
<span className="text-sm font-medium text-foreground block">Yojo</span>
<span className="text-[10px] text-muted-foreground"></span>
<span className="text-sm font-medium text-foreground block">{t("footer.company")}</span>
<span className="text-[10px] text-muted-foreground">{t("footer.companyFull")}</span>
</div>
</div>
<div className="flex items-center gap-6">
<a href="#" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
github
{t("footer.github")}
</a>
<a href="#" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
twitter
{t("footer.twitter")}
</a>
<a href="#" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
blog
{t("footer.blog")}
</a>
</div>
<span className="text-[10px] text-muted-foreground">
© {new Date().getFullYear()} all rights reserved
© {new Date().getFullYear()} {t("footer.copyright")}
</span>
</div>
</div>

View File

@@ -1,70 +1,66 @@
import { useTranslation } from "react-i18next"
import { ArrowDownRight, Github, Twitter, Mail } from "lucide-react"
export function Hero() {
const { t } = useTranslation()
const techStack = ["React Native", "Go", "Rust", "Taro", "C#", "React", "TypeScript", "PostgreSQL"]
return (
<section id="hero" className="min-h-screen flex flex-col justify-center pt-14 relative overflow-hidden">
{/* Background grid */}
<div className="absolute inset-0 opacity-[0.02]" style={{
backgroundImage: `linear-gradient(rgba(250,250,250,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(250,250,250,0.5) 1px, transparent 1px)`,
backgroundSize: '60px 60px'
}} />
{/* Glow effects */}
<div className="absolute top-1/4 right-1/4 w-96 h-96 bg-accent/5 rounded-full blur-[120px]" />
<div className="absolute bottom-1/4 left-1/4 w-64 h-64 bg-accent/3 rounded-full blur-[100px]" />
<div className="max-w-6xl mx-auto px-6 py-24 w-full relative">
<div className="grid grid-cols-1 lg:grid-cols-5 gap-16 items-center">
{/* Left: Text - takes 3 cols */}
<div className="lg:col-span-3">
{/* HIT Badge */}
<div className="inline-flex items-center gap-2.5 px-3 py-1.5 rounded-full bg-accent/10 border border-accent/20 mb-8">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-accent opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-accent" />
</span>
<span className="text-[11px] font-medium text-accent tracking-wide"></span>
<span className="text-[11px] font-medium text-accent tracking-wide">{t("hero.badge")}</span>
</div>
{/* Main heading */}
<h1 className="text-5xl sm:text-6xl lg:text-7xl font-bold text-foreground leading-[1.1] mb-6 tracking-tight">
<span className="block text-muted-foreground mt-2"></span>
{t("hero.heading1")}
<span className="block text-muted-foreground mt-2">{t("hero.heading2")}</span>
</h1>
{/* Subtitle line */}
<div className="flex items-center gap-3 mb-8">
<div className="h-px w-12 bg-accent/50" />
<span className="text-xs text-accent font-medium tracking-wide uppercase">Weihai, China · </span>
<span className="text-xs text-accent font-medium tracking-wide uppercase">{t("hero.subtitle")}</span>
</div>
{/* Description */}
<p className="text-sm text-muted-foreground leading-relaxed mb-10 max-w-lg">
0 1
{t("hero.description")}
</p>
{/* CTA Buttons */}
<div className="flex items-center gap-4 mb-12">
<a
href="#cases"
className="group inline-flex items-center gap-2 text-xs font-medium text-background bg-foreground px-5 py-2.5 rounded-md hover:bg-foreground/90 transition-all"
>
{t("hero.viewProjects")}
<ArrowDownRight className="w-3.5 h-3.5 transition-transform group-hover:translate-x-0.5 group-hover:translate-y-0.5" />
</a>
<a
href="#about"
className="inline-flex items-center gap-2 text-xs font-medium text-muted-foreground border border-border px-5 py-2.5 rounded-md hover:border-foreground/30 hover:text-foreground transition-all"
>
{t("hero.learnMore")}
</a>
</div>
{/* Tech stack */}
<div>
<span className="text-[10px] text-muted-foreground uppercase tracking-widest mb-3 block">tech stack</span>
<span className="text-[10px] text-muted-foreground uppercase tracking-widest mb-3 block">{t("hero.techStack")}</span>
<div className="flex flex-wrap gap-2">
{["React Native", "Go", "Rust", "Taro", "C#", "React", "TypeScript", "PostgreSQL"].map((tech) => (
{techStack.map((tech) => (
<span
key={tech}
className="px-3 py-1.5 text-[11px] text-muted-foreground bg-secondary/50 border border-border/50 rounded-md hover:border-accent/30 hover:text-accent transition-all cursor-default"
@@ -76,61 +72,54 @@ export function Hero() {
</div>
</div>
{/* Right: Terminal - takes 2 cols */}
<div className="lg:col-span-2">
<div className="rounded-xl overflow-hidden border border-border/50 bg-card/50 backdrop-blur-sm">
{/* Terminal header */}
<div className="flex items-center gap-2 px-4 py-3 bg-secondary/30 border-b border-border/50">
<div className="flex items-center gap-1.5">
<div className="w-3 h-3 rounded-full bg-red-500/60" />
<div className="w-3 h-3 rounded-full bg-yellow-500/60" />
<div className="w-3 h-3 rounded-full bg-green-500/60" />
</div>
<span className="ml-3 text-[10px] text-muted-foreground font-mono">qczl zsh 80x24</span>
<span className="ml-3 text-[10px] text-muted-foreground font-mono">{t("hero.terminal.title")}</span>
</div>
{/* Terminal body */}
<div className="p-5 font-mono text-[11px] leading-relaxed">
<div className="space-y-3">
<div>
<span className="text-accent"></span>
<span className="text-muted-foreground ml-2">~</span>
<span className="text-foreground ml-2">whoami</span>
<span className="text-foreground ml-2">{t("hero.terminal.whoami")}</span>
</div>
<div className="text-foreground pl-6">
qczl-tech HIT incubated startup
{t("hero.terminal.whoamiOutput")}
</div>
<div className="pt-1">
<span className="text-accent"></span>
<span className="text-muted-foreground ml-2">~</span>
<span className="text-foreground ml-2">cat products.json</span>
<span className="text-foreground ml-2">{t("hero.terminal.catCmd")}</span>
</div>
<div className="pl-6 text-foreground">
<pre className="text-[10px] leading-5">{`{
"威友": "campus social app",
"carroterp": "internal ERP system",
"carrotprint": "self-service printing"
}`}</pre>
<pre className="text-[10px] leading-5">{t("hero.terminal.catOutput")}</pre>
</div>
<div className="pt-1">
<span className="text-accent"></span>
<span className="text-muted-foreground ml-2">~</span>
<span className="text-foreground ml-2">npm run stats</span>
<span className="text-foreground ml-2">{t("hero.terminal.statsCmd")}</span>
</div>
<div className="pl-6 space-y-1">
<div className="flex items-center gap-2">
<span className="text-muted-foreground">platforms:</span>
<span className="text-accent font-semibold">iOS + Android + Web + MiniApp</span>
<span className="text-muted-foreground">{t("hero.terminal.statsPlatforms")}</span>
<span className="text-accent font-semibold">{t("hero.terminal.statsPlatformsValue")}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-muted-foreground">stack:</span>
<span className="text-accent font-semibold">RN + Go + Rust + Taro</span>
<span className="text-muted-foreground">{t("hero.terminal.statsStack")}</span>
<span className="text-accent font-semibold">{t("hero.terminal.statsStackValue")}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-muted-foreground">origin:</span>
<span className="text-accent font-semibold">Harbin Institute of Technology</span>
<span className="text-muted-foreground">{t("hero.terminal.statsOrigin")}</span>
<span className="text-accent font-semibold">{t("hero.terminal.statsOriginValue")}</span>
</div>
</div>
@@ -145,7 +134,6 @@ export function Hero() {
</div>
</div>
{/* Bottom bar */}
<div className="flex items-center justify-between mt-20 pt-8 border-t border-border/30">
<div className="flex items-center gap-6">
<a href="#" className="text-muted-foreground hover:text-foreground transition-colors">
@@ -158,7 +146,7 @@ export function Hero() {
<Mail className="w-4 h-4" />
</a>
</div>
<span className="text-[10px] text-muted-foreground">scroll to explore </span>
<span className="text-[10px] text-muted-foreground">{t("hero.scrollHint")}</span>
</div>
</div>
</section>

View File

@@ -1,19 +1,23 @@
import { useState, useEffect } from "react"
import { useState, useEffect, useMemo } from "react"
import { useTranslation } from "react-i18next"
import { Menu, X, Terminal } from "lucide-react"
import { cn } from "../../lib/utils"
const navItems = [
{ label: "home", href: "#hero" },
{ label: "about", href: "#about" },
{ label: "work", href: "#cases" },
{ label: "team", href: "#team" },
]
const SECTION_HREFS = ["#hero", "#about", "#cases", "#team"] as const
export function Navbar() {
const { t, i18n } = useTranslation()
const [isScrolled, setIsScrolled] = useState(false)
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const [activeSection, setActiveSection] = useState("hero")
const navItems = useMemo(() => [
{ label: t("nav.home"), href: SECTION_HREFS[0] },
{ label: t("nav.about"), href: SECTION_HREFS[1] },
{ label: t("nav.work"), href: SECTION_HREFS[2] },
{ label: t("nav.team"), href: SECTION_HREFS[3] },
], [t])
useEffect(() => {
const handleScroll = () => setIsScrolled(window.scrollY > 10)
window.addEventListener("scroll", handleScroll)
@@ -31,13 +35,17 @@ export function Navbar() {
},
{ threshold: 0.3 }
)
navItems.forEach((item) => {
const el = document.querySelector(item.href)
SECTION_HREFS.forEach((href) => {
const el = document.querySelector(href)
if (el) observer.observe(el)
})
return () => observer.disconnect()
}, [])
const toggleLanguage = () => {
i18n.changeLanguage(i18n.language === "zh" ? "en" : "zh")
}
return (
<header
className={cn(
@@ -47,7 +55,6 @@ export function Navbar() {
: "bg-transparent border-b border-transparent"
)}
>
{/* 顶部绿色光晕装饰线 */}
<div
className={cn(
"absolute top-0 left-0 right-0 h-[1px] transition-opacity duration-500",
@@ -61,11 +68,9 @@ export function Navbar() {
<div className="max-w-6xl mx-auto px-6">
<div className="flex items-center justify-between h-16">
{/* Logo */}
<a href="#hero" className="flex items-center gap-3 group relative">
<div className="relative w-8 h-8 rounded-lg bg-accent/10 border border-accent/30 flex items-center justify-center group-hover:bg-accent/20 transition-all duration-300 group-hover:shadow-[0_0_15px_rgba(34,197,94,0.2)]">
<Terminal className="w-4 h-4 text-accent transition-transform duration-300 group-hover:scale-110" />
{/* Logo 呼吸光晕 */}
<div className="absolute inset-0 rounded-lg bg-accent/5 animate-ping opacity-20" />
</div>
<div className="flex flex-col">
@@ -78,53 +83,66 @@ export function Navbar() {
</div>
</a>
{/* Desktop Nav */}
<nav className="hidden md:flex items-center gap-1 bg-secondary/30 backdrop-blur-sm rounded-full px-2 py-1 border border-border/20">
{navItems.map((item) => {
const isActive = activeSection === item.href.slice(1)
return (
<a
key={item.href}
href={item.href}
className={cn(
"relative px-4 py-1.5 text-xs rounded-full transition-all duration-300",
isActive
? "text-foreground font-medium"
: "text-muted-foreground hover:text-foreground"
)}
>
{isActive && (
<span className="absolute inset-0 bg-secondary rounded-full border border-border/40 shadow-sm" />
)}
<span className="relative z-10 font-[system-ui,-apple-system,BlinkMacSystemFont,'Segoe_UI',Roboto,sans-serif] tracking-wide">{item.label}</span>
</a>
)
})}
</nav>
<div className="hidden md:flex items-center gap-3">
<nav className="flex items-center gap-1 bg-secondary/30 backdrop-blur-sm rounded-full px-2 py-1 border border-border/20">
{navItems.map((item) => {
const isActive = activeSection === item.href.slice(1)
return (
<a
key={item.href}
href={item.href}
className={cn(
"relative px-4 py-1.5 text-xs rounded-full transition-all duration-300",
isActive
? "text-foreground font-medium"
: "text-muted-foreground hover:text-foreground"
)}
>
{isActive && (
<span className="absolute inset-0 bg-secondary rounded-full border border-border/40 shadow-sm" />
)}
<span className="relative z-10 font-[system-ui,-apple-system,BlinkMacSystemFont,'Segoe_UI',Roboto,sans-serif] tracking-wide">{item.label}</span>
</a>
)
})}
</nav>
{/* CTA Button */}
<a
href="mailto:system@qczlit.cn"
className="hidden md:inline-flex items-center gap-2 text-xs font-medium px-5 py-2 rounded-full border border-accent/30 bg-accent/10 text-accent hover:bg-accent hover:text-accent-foreground transition-all duration-300 hover:shadow-[0_0_20px_rgba(34,197,94,0.3)]"
>
<span className="relative flex h-1.5 w-1.5">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-accent opacity-75" />
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-accent" />
</span>
<span className="font-[system-ui,-apple-system,BlinkMacSystemFont,'Segoe_UI',Roboto,sans-serif]">let's talk</span>
</a>
<a
href="mailto:system@qczlit.cn"
className="hidden md:inline-flex items-center gap-2 text-xs font-medium px-5 py-2 rounded-full border border-accent/30 bg-accent/10 text-accent hover:bg-accent hover:text-accent-foreground transition-all duration-300 hover:shadow-[0_0_20px_rgba(34,197,94,0.3)]"
>
<span className="relative flex h-1.5 w-1.5">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-accent opacity-75" />
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-accent" />
</span>
<span className="font-[system-ui,-apple-system,BlinkMacSystemFont,'Segoe_UI',Roboto,sans-serif]">{t("nav.letsTalk")}</span>
</a>
{/* Mobile Toggle */}
<button
className="md:hidden relative p-2 text-muted-foreground hover:text-foreground rounded-lg hover:bg-secondary/50 transition-colors"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
>
{isMobileMenuOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>
<button
onClick={toggleLanguage}
className="text-[11px] font-medium text-muted-foreground hover:text-foreground px-2.5 py-1.5 rounded-full border border-border/20 hover:border-border/50 transition-all"
>
{i18n.language === "zh" ? "EN" : "中"}
</button>
</div>
<div className="md:hidden flex items-center gap-2">
<button
onClick={toggleLanguage}
className="text-[11px] font-medium text-muted-foreground hover:text-foreground px-2 py-1 rounded border border-border/20 hover:border-border/50 transition-all"
>
{i18n.language === "zh" ? "EN" : "中"}
</button>
<button
className="relative p-2 text-muted-foreground hover:text-foreground rounded-lg hover:bg-secondary/50 transition-colors"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
>
{isMobileMenuOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>
</div>
</div>
</div>
{/* Mobile Menu */}
<div
className={cn(
"md:hidden overflow-hidden transition-all duration-300 ease-in-out",
@@ -150,7 +168,7 @@ export function Navbar() {
href="mailto:system@qczlit.cn"
className="flex items-center justify-center gap-2 text-sm font-medium px-4 py-3 rounded-xl bg-accent text-accent-foreground hover:bg-accent/90 transition-colors"
>
<span className="font-[system-ui,-apple-system,BlinkMacSystemFont,'Segoe_UI',Roboto,sans-serif] tracking-wide">let's talk</span>
<span className="font-[system-ui,-apple-system,BlinkMacSystemFont,'Segoe_UI',Roboto,sans-serif] tracking-wide">{t("nav.letsTalk")}</span>
</a>
</div>
</div>

View File

@@ -1,4 +1,5 @@
import { useEffect, useRef } from "react"
import { useTranslation } from "react-i18next"
import {
Code2,
Smartphone,
@@ -9,60 +10,36 @@ import {
ArrowUpRight,
} from "lucide-react"
const services = [
{
icon: Code2,
title: "企业级应用",
subtitle: "Enterprise Applications",
description: "基于现代化技术栈,为企业打造高性能、可扩展的业务管理系统。",
color: "from-blue-500/10 to-blue-600/5",
iconColor: "text-blue-600",
},
{
icon: Smartphone,
title: "移动应用",
subtitle: "Mobile Applications",
description: "iOS 与 Android 原生及跨平台应用开发,覆盖全终端用户体验。",
color: "from-purple-500/10 to-purple-600/5",
iconColor: "text-purple-600",
},
{
icon: Globe,
title: "Web 前端",
subtitle: "Web Development",
description: "响应式网站与单页应用开发,注重用户体验与交互设计。",
color: "from-orange-500/10 to-orange-600/5",
iconColor: "text-orange-600",
},
{
icon: Database,
title: "后端架构",
subtitle: "Backend Systems",
description: "高并发后端服务架构设计与数据库优化,保障系统稳定运行。",
color: "from-green-500/10 to-green-600/5",
iconColor: "text-green-600",
},
{
icon: Cloud,
title: "云服务",
subtitle: "Cloud Services",
description: "云原生架构设计与容器化部署方案,实现弹性伸缩与自动化运维。",
color: "from-sky-500/10 to-sky-600/5",
iconColor: "text-sky-600",
},
{
icon: Settings,
title: "运维支持",
subtitle: "DevOps Support",
description: "7×24小时系统监控与技术支持服务及时响应并解决各类问题。",
color: "from-rose-500/10 to-rose-600/5",
iconColor: "text-rose-600",
},
const serviceIcons = [Code2, Smartphone, Globe, Database, Cloud, Settings] as const
const serviceColors = [
"from-blue-500/10 to-blue-600/5",
"from-purple-500/10 to-purple-600/5",
"from-orange-500/10 to-orange-600/5",
"from-green-500/10 to-green-600/5",
"from-sky-500/10 to-sky-600/5",
"from-rose-500/10 to-rose-600/5",
]
const serviceIconColors = [
"text-blue-600",
"text-purple-600",
"text-orange-600",
"text-green-600",
"text-sky-600",
"text-rose-600",
]
export function Services() {
const { t } = useTranslation()
const sectionRef = useRef<HTMLDivElement>(null)
type ServiceItem = {
title: string
subtitle: string
description: string
}
const items = t("services.items", { returnObjects: true }) as ServiceItem[]
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
@@ -84,54 +61,51 @@ export function Services() {
return (
<section id="services" ref={sectionRef} className="py-32 bg-white">
<div className="max-w-[980px] mx-auto px-6">
{/* Section Header */}
<div className="text-center mb-20">
<h2 className="reveal opacity-0 translate-y-6 transition-all duration-700 text-4xl sm:text-5xl font-semibold text-[#1d1d1f] mb-4 tracking-tight">
{t("services.title")}
</h2>
<p className="reveal opacity-0 translate-y-6 transition-all duration-700 delay-100 text-lg text-[#86868b] max-w-md mx-auto">
{t("services.description")}
</p>
</div>
{/* Services Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
{services.map((service, index) => (
<div
key={service.title}
className="reveal opacity-0 translate-y-6 transition-all duration-700 group relative"
style={{ transitionDelay: `${150 + index * 100}ms` }}
>
<div className="relative h-full p-8 rounded-3xl bg-[#fbfbfd] border border-transparent hover:border-border/50 transition-all duration-500 hover:shadow-xl hover:shadow-black/5 hover:-translate-y-1">
{/* Gradient Background */}
<div className={`absolute inset-0 rounded-3xl bg-gradient-to-br ${service.color} opacity-0 group-hover:opacity-100 transition-opacity duration-500`} />
<div className="relative">
{/* Icon */}
<div className="w-12 h-12 rounded-2xl bg-white shadow-sm shadow-black/5 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-500">
<service.icon className={`w-5 h-5 ${service.iconColor}`} />
</div>
{Array.isArray(items) && items.map((service, index) => {
const Icon = serviceIcons[index]
return (
<div
key={service.title}
className="reveal opacity-0 translate-y-6 transition-all duration-700 group relative"
style={{ transitionDelay: `${150 + index * 100}ms` }}
>
<div className="relative h-full p-8 rounded-3xl bg-[#fbfbfd] border border-transparent hover:border-border/50 transition-all duration-500 hover:shadow-xl hover:shadow-black/5 hover:-translate-y-1">
<div className={`absolute inset-0 rounded-3xl bg-gradient-to-br ${serviceColors[index]} opacity-0 group-hover:opacity-100 transition-opacity duration-500`} />
{/* Content */}
<h3 className="text-lg font-semibold text-[#1d1d1f] mb-1">
{service.title}
</h3>
<p className="text-xs text-[#86868b] uppercase tracking-wider mb-4">
{service.subtitle}
</p>
<p className="text-sm text-[#86868b] leading-relaxed mb-6">
{service.description}
</p>
<div className="relative">
<div className="w-12 h-12 rounded-2xl bg-white shadow-sm shadow-black/5 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-500">
<Icon className={`w-5 h-5 ${serviceIconColors[index]}`} />
</div>
{/* Link */}
<div className="flex items-center gap-1 text-sm font-medium text-[#0071e3] opacity-0 group-hover:opacity-100 transition-all duration-300 translate-y-2 group-hover:translate-y-0">
<ArrowUpRight className="w-3.5 h-3.5" />
<h3 className="text-lg font-semibold text-[#1d1d1f] mb-1">
{service.title}
</h3>
<p className="text-xs text-[#86868b] uppercase tracking-wider mb-4">
{service.subtitle}
</p>
<p className="text-sm text-[#86868b] leading-relaxed mb-6">
{service.description}
</p>
<div className="flex items-center gap-1 text-sm font-medium text-[#0071e3] opacity-0 group-hover:opacity-100 transition-all duration-300 translate-y-2 group-hover:translate-y-0">
{t("services.learnMore")}
<ArrowUpRight className="w-3.5 h-3.5" />
</div>
</div>
</div>
</div>
</div>
))}
)
})}
</div>
</div>

View File

@@ -1,66 +1,44 @@
import { useState } from "react"
import { useTranslation } from "react-i18next"
const team = [
{
name: "吴彧博",
handle: "@wuyubo",
role: "技术经理",
bio: "统筹技术架构与团队管理,把控项目质量与交付节奏。全栈背景,擅长系统设计与技术决策。",
skills: ["Go", "Rust", "K8s"],
color: "#10b981",
},
{
name: "郝咏",
handle: "@haoyong",
role: "前端负责人",
bio: "专注跨平台前端开发React Native 与 Taro 专家。追求极致的用户体验与性能优化。",
skills: ["React Native", "Taro", "TypeScript"],
color: "#3b82f6",
},
{
name: "吴翔宇",
handle: "@wuxiangyu",
role: "后端负责人",
bio: "高并发系统架构师Go 语言专家。负责服务端架构设计与数据库优化,保障系统稳定。",
skills: ["Go", "PostgreSQL", "Redis"],
color: "#f97316",
},
{
name: "韩惠子",
handle: "@hanhuizi",
role: "产品经理",
bio: "负责产品规划与需求分析,连接技术与业务。善于将复杂需求转化为清晰的产品方案。",
skills: ["Figma", "Product", "Data"],
color: "#a855f7",
},
]
const memberColors = ["#10b981", "#3b82f6", "#f97316", "#a855f7"]
export function Team() {
const { t } = useTranslation()
const [active, setActive] = useState(0)
type Member = {
name: string
handle: string
role: string
bio: string
skills: string[]
}
const members = t("team.members", { returnObjects: true }) as Member[]
return (
<section id="team" className="py-32 relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-accent/[0.02] to-transparent" />
<div className="max-w-6xl mx-auto px-6 relative">
{/* Section header */}
<div className="mb-20">
<div className="flex items-center gap-3 mb-6">
<div className="h-px w-8 bg-accent/50" />
<span className="text-[11px] text-accent font-medium tracking-widest uppercase">the team</span>
<span className="text-[11px] text-accent font-medium tracking-widest uppercase">{t("team.label")}</span>
</div>
<h2 className="text-4xl sm:text-5xl font-bold text-foreground mb-6 tracking-tight">
{t("team.title")}
</h2>
<p className="text-base text-muted-foreground leading-relaxed max-w-2xl">
{t("team.description")}
</p>
</div>
{/* Horizontal expanding cards */}
<div className="flex flex-col lg:flex-row gap-3 min-h-[420px]">
{team.map((member, index) => {
{Array.isArray(members) && members.map((member, index) => {
const isActive = active === index
const color = memberColors[index]
return (
<div
@@ -79,16 +57,14 @@ export function Team() {
: "border-border/20 hover:border-border/30"
}`}
>
{/* Background glow */}
<div
className={`absolute inset-0 pointer-events-none transition-opacity duration-500 ${isActive ? "opacity-100" : "opacity-0"}`}
style={{
background: `radial-gradient(ellipse at 80% 100%, ${member.color}08, transparent 60%)`,
background: `radial-gradient(ellipse at 80% 100%, ${color}08, transparent 60%)`,
}}
/>
<div className="relative">
{/* Name & role */}
<div className="mb-4">
<h3 className="text-lg font-bold text-foreground tracking-tight mb-1">
{member.name}
@@ -98,9 +74,9 @@ export function Team() {
<span
className="px-2 py-0.5 text-[9px] font-semibold rounded-full border shrink-0"
style={{
color: member.color,
borderColor: `${member.color}40`,
backgroundColor: `${member.color}10`,
color: color,
borderColor: `${color}40`,
backgroundColor: `${color}10`,
}}
>
{member.role}
@@ -108,7 +84,6 @@ export function Team() {
</div>
</div>
{/* Bio - expands on active */}
<p
className={`text-muted-foreground leading-relaxed transition-all duration-500 ${
isActive
@@ -120,7 +95,6 @@ export function Team() {
</p>
</div>
{/* Skills */}
<div className="relative flex flex-wrap gap-1.5 mt-4">
{member.skills.map((s) => (
<span
@@ -134,10 +108,9 @@ export function Team() {
))}
</div>
{/* Bottom accent line */}
<div
className={`absolute bottom-0 left-6 right-6 h-[2px] rounded-full transition-all duration-500 ${isActive ? "opacity-100" : "opacity-0"}`}
style={{ background: `linear-gradient(90deg, transparent, ${member.color}, transparent)` }}
style={{ background: `linear-gradient(90deg, transparent, ${color}, transparent)` }}
/>
</div>
</div>

View File

@@ -1,56 +0,0 @@
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cn } from "../../lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-8 px-4 py-2",
sm: "h-7 px-3 text-xs",
lg: "h-9 px-6",
icon: "h-8 w-8",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -1,75 +0,0 @@
import * as React from "react"
import { cn } from "../../lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"bg-card text-card-foreground border border-border",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-5", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-5 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-5 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@@ -1,21 +0,0 @@
import * as React from "react"
import { cn } from "../../lib/utils"
const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

View File

@@ -1,20 +0,0 @@
import * as React from "react"
import { cn } from "../../lib/utils"
const Textarea = React.forwardRef<HTMLTextAreaElement, React.TextareaHTMLAttributes<HTMLTextAreaElement>>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"
export { Textarea }

21
src/i18n/index.ts Normal file
View File

@@ -0,0 +1,21 @@
import i18n from "i18next"
import { initReactI18next } from "react-i18next"
import LanguageDetector from "i18next-browser-languagedetector"
import zh from "./locales/zh"
import en from "./locales/en"
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
zh: { translation: zh },
en: { translation: en },
},
fallbackLng: "zh",
interpolation: {
escapeValue: false,
},
})
export default i18n

191
src/i18n/locales/en.ts Normal file
View File

@@ -0,0 +1,191 @@
export default {
site: {
title: "Qingchunzhilv Electronic Information Technology (Weihai) Co., Ltd.",
subtitle: "Professional Software Development Service Provider",
},
nav: {
home: "home",
about: "about",
work: "work",
team: "team",
letsTalk: "let's talk",
},
hero: {
badge: "HIT Incubated Startup",
heading1: "QCZL Tech",
heading2: "Electronic Information Technology",
subtitle: "Weihai, China · Harbin Institute of Technology",
description: "An independent tech team from HIT, focused on deep software products. From architecture design to code implementation, we build reliable digital products from 0 to 1.",
viewProjects: "View Projects",
learnMore: "Learn More",
techStack: "tech stack",
terminal: {
title: "qczl — zsh — 80x24",
whoami: "whoami",
whoamiOutput: "qczl-tech — HIT incubated startup",
catCmd: "cat products.json",
catOutput: `{\n "威友": "campus social app",\n "carroterp": "internal ERP system",\n "carrotprint": "self-service printing"\n}`,
statsCmd: "npm run stats",
statsPlatforms: "platforms:",
statsStack: "stack:",
statsOrigin: "origin:",
statsPlatformsValue: "iOS + Android + Web + MiniApp",
statsStackValue: "RN + Go + Rust + Taro",
statsOriginValue: "Harbin Institute of Technology",
},
scrollHint: "scroll to explore ↓",
},
about: {
label: "about us",
title: "Not an Outsourcing Company",
description: "We are a small team of full-stack engineers who believe small teams make great products. No layers of project managers — you talk directly to the people writing the code.",
stats: [
{ value: "100+", label: "projects shipped", desc: "delivered" },
{ value: "5+", label: "years building", desc: "in development" },
{ value: "95%", label: "client retention", desc: "retention rate" },
],
whyUs: {
label: "why us",
items: [
{ title: "AI-Powered", desc: "Deep AI toolchain integration, boosting development efficiency several times over" },
{ title: "Fast Delivery", desc: "Two-week iterations, continuously shipping working results" },
{ title: "Full-Stack Coverage", desc: "Frontend, backend, mobile, infrastructure — all handled by one team" },
{ title: "Direct Communication", desc: "You talk directly with developers, zero requirement loss" },
],
},
principles: {
label: "our principles",
items: [
{ title: "Clean Code", desc: "Code is documentation. We write human-readable code." },
{ title: "Ship Fast", desc: "Two-week iterations, validate ideas quickly." },
{ title: "Own It", desc: "Full-stack ownership. From requirements to deployment." },
{ title: "Transparency", desc: "Fully transparent progress, ready to review code and deployment anytime." },
{ title: "Craftsmanship", desc: "Treat every project like our own work." },
{ title: "Scale Ready", desc: "Architecture designed for growth — today's small project, tomorrow's platform." },
],
},
},
cases: {
label: "selected work",
title: "Projects",
description: "Our self-developed core products, each battle-tested in real-world scenarios.",
items: [
{
id: "01",
title: "withyou",
subtitle: "withyou",
category: "Campus Social",
description: "A campus social app for college students. Supports interest matching, event posting, anonymous posts, covering iOS, Android, and Web admin panel.",
platforms: ["iOS", "Android", "Web"],
tags: ["React Native", "Go", "PostgreSQL"],
status: "live",
},
{
id: "02",
title: "CarrotERP",
subtitle: "Enterprise Resource Planning",
category: "Enterprise Management",
description: "In-house ERP software benchmarked against industry leaders. Covers procurement, inventory, finance, HR and other core modules for daily operations.",
platforms: ["Web", "Desktop"],
tags: ["React", "Go", "PostgreSQL"],
status: "live",
},
{
id: "03",
title: "CarrotPrint",
subtitle: "Self-service Printing",
category: "Self-service",
description: "Self-service printing solution covering WeChat Mini Program, Windows client, and Linux server. Users scan to upload, print at nearest location.",
platforms: ["MiniApp", "Windows", "Linux"],
tags: ["Rust", "C#", "Taro", "Go"],
status: "live",
},
],
techStack: "tech stack",
},
team: {
label: "the team",
title: "Core Members",
description: "A HIT-rooted tech team — small but elite, each member an expert in their domain.",
members: [
{
name: "吴彧博",
handle: "@wuyubo",
role: "Tech Lead",
bio: "Oversees technical architecture and team management, ensuring project quality and delivery cadence. Full-stack background, skilled in system design and technical decisions.",
skills: ["Go", "Rust", "K8s"],
},
{
name: "郝咏",
handle: "@haoyong",
role: "Frontend Lead",
bio: "Focused on cross-platform frontend development, React Native and Taro expert. Pursues ultimate user experience and performance optimization.",
skills: ["React Native", "Taro", "TypeScript"],
},
{
name: "吴翔宇",
handle: "@wuxiangyu",
role: "Backend Lead",
bio: "High-concurrency system architect and Go language expert. Responsible for server-side architecture design and database optimization.",
skills: ["Go", "PostgreSQL", "Redis"],
},
{
name: "韩惠子",
handle: "@hanhuizi",
role: "Product Manager",
bio: "Responsible for product planning and requirements analysis, bridging technology and business. Skilled at transforming complex requirements into clear product solutions.",
skills: ["Figma", "Product", "Data"],
},
],
},
services: {
title: "Our Services",
description: "Comprehensive software development capabilities to power digital transformation",
learnMore: "Learn More",
items: [
{
title: "Enterprise Apps",
subtitle: "Enterprise Applications",
description: "Building high-performance, scalable business management systems with modern tech stacks.",
},
{
title: "Mobile Apps",
subtitle: "Mobile Applications",
description: "Native and cross-platform app development for iOS and Android, covering the full user experience.",
},
{
title: "Web Frontend",
subtitle: "Web Development",
description: "Responsive websites and single-page applications, focused on UX and interaction design.",
},
{
title: "Backend Systems",
subtitle: "Backend Architecture",
description: "High-concurrency backend service architecture design and database optimization for stable operations.",
},
{
title: "Cloud Services",
subtitle: "Cloud Services",
description: "Cloud-native architecture design and containerized deployment for elastic scaling and automated operations.",
},
{
title: "DevOps",
subtitle: "DevOps Support",
description: "24/7 system monitoring and technical support, rapid response and issue resolution.",
},
],
},
footer: {
label: "get in touch",
title: "Have a project in mind?",
description: "We welcome interesting challenges anytime. Email us directly, we'll respond within 24 hours. No BS, no sales pitch, just tech talk.",
email: "system@qczlit.cn",
company: "Yojo",
companyFull: "Qingchunzhilv Electronic Information Technology (Weihai) Co., Ltd.",
github: "github",
twitter: "twitter",
blog: "blog",
copyright: "all rights reserved",
},
language: "Language",
}

191
src/i18n/locales/zh.ts Normal file
View File

@@ -0,0 +1,191 @@
export default {
site: {
title: "青春之旅电子信息科技(威海)有限公司",
subtitle: "专业软件开发服务提供商",
},
nav: {
home: "home",
about: "about",
work: "work",
team: "team",
letsTalk: "let's talk",
},
hero: {
badge: "哈工大孵化创业项目",
heading1: "青春之旅",
heading2: "电子信息科技",
subtitle: "Weihai, China · 哈尔滨工业大学",
description: "源自哈工大的独立技术团队,专注有深度的软件产品。从架构设计到代码实现,从 0 到 1 构建可靠的数字产品。",
viewProjects: "查看项目",
learnMore: "了解更多",
techStack: "tech stack",
terminal: {
title: "qczl — zsh — 80x24",
whoami: "whoami",
whoamiOutput: "qczl-tech — HIT incubated startup",
catCmd: "cat products.json",
catOutput: `{\n "威友": "campus social app",\n "carroterp": "internal ERP system",\n "carrotprint": "self-service printing"\n}`,
statsCmd: "npm run stats",
statsPlatforms: "platforms:",
statsStack: "stack:",
statsOrigin: "origin:",
statsPlatformsValue: "iOS + Android + Web + MiniApp",
statsStackValue: "RN + Go + Rust + Taro",
statsOriginValue: "Harbin Institute of Technology",
},
scrollHint: "scroll to explore ↓",
},
about: {
label: "about us",
title: "不是外包公司",
description: "我们是一个由全栈工程师组成的小团队,相信小团队能做出好产品。没有项目经理层层转达,你直接和写代码的人对话。",
stats: [
{ value: "100+", label: "projects shipped", desc: "交付项目" },
{ value: "5+", label: "years building", desc: "持续开发" },
{ value: "95%", label: "client retention", desc: "客户留存" },
],
whyUs: {
label: "why us",
items: [
{ title: "AI 驱动", desc: "深度整合 AI 工具链,开发效率提升数倍" },
{ title: "快速交付", desc: "两周一个迭代,持续交付可运行的成果" },
{ title: "全栈覆盖", desc: "前端、后端、移动端、基础设施一个团队搞定" },
{ title: "直接沟通", desc: "你直接和写代码的人对话,需求零损耗" },
],
},
principles: {
label: "our principles",
items: [
{ title: "Clean Code", desc: "代码即文档。我们写人类可读的代码。" },
{ title: "Ship Fast", desc: "两周一个迭代,快速验证想法。" },
{ title: "Own It", desc: "全栈负责到底。从需求到部署。" },
{ title: "Transparency", desc: "进度完全透明,随时可查看代码和部署。" },
{ title: "Craftsmanship", desc: "把每个项目当作自己的作品。" },
{ title: "Scale Ready", desc: "架构为增长设计,今天的小项目明天的大平台。" },
],
},
},
cases: {
label: "selected work",
title: "项目案例",
description: "以下是我们自主研发的核心产品,每个项目都经过真实场景验证。",
items: [
{
id: "01",
title: "威友",
subtitle: "withyou",
category: "校园社交",
description: "面向高校学生的校园交友 APP。支持兴趣匹配、活动发布、匿名树洞等功能覆盖 iOS、Android 双端及 Web 管理后台。",
platforms: ["iOS", "Android", "Web"],
tags: ["React Native", "Go", "PostgreSQL"],
status: "live",
},
{
id: "02",
title: "CarrotERP",
subtitle: "Enterprise Resource Planning",
category: "企业管理",
description: "内部使用的 ERP 管理软件,对标行业领军产品。涵盖采购、库存、财务、人事等核心模块,支撑企业日常运营全流程。",
platforms: ["Web", "Desktop"],
tags: ["React", "Go", "PostgreSQL"],
status: "live",
},
{
id: "03",
title: "CarrotPrint",
subtitle: "Self-service Printing",
category: "自助服务",
description: "自助打印解决方案覆盖微信小程序、Windows 客户端、Linux 服务端三端。用户扫码上传文件,就近打印取件。",
platforms: ["小程序", "Windows", "Linux"],
tags: ["Rust", "C#", "Taro", "Go"],
status: "live",
},
],
techStack: "tech stack",
},
team: {
label: "the team",
title: "核心成员",
description: "哈工大背景的技术团队,小而精,每个人都是各自领域的专家。",
members: [
{
name: "吴彧博",
handle: "@wuyubo",
role: "技术经理",
bio: "统筹技术架构与团队管理,把控项目质量与交付节奏。全栈背景,擅长系统设计与技术决策。",
skills: ["Go", "Rust", "K8s"],
},
{
name: "郝咏",
handle: "@haoyong",
role: "前端负责人",
bio: "专注跨平台前端开发React Native 与 Taro 专家。追求极致的用户体验与性能优化。",
skills: ["React Native", "Taro", "TypeScript"],
},
{
name: "吴翔宇",
handle: "@wuxiangyu",
role: "后端负责人",
bio: "高并发系统架构师Go 语言专家。负责服务端架构设计与数据库优化,保障系统稳定。",
skills: ["Go", "PostgreSQL", "Redis"],
},
{
name: "韩惠子",
handle: "@hanhuizi",
role: "产品经理",
bio: "负责产品规划与需求分析,连接技术与业务。善于将复杂需求转化为清晰的产品方案。",
skills: ["Figma", "Product", "Data"],
},
],
},
services: {
title: "我们的服务",
description: "全方位的软件开发能力,助力企业数字化转型",
learnMore: "了解更多",
items: [
{
title: "企业级应用",
subtitle: "Enterprise Applications",
description: "基于现代化技术栈,为企业打造高性能、可扩展的业务管理系统。",
},
{
title: "移动应用",
subtitle: "Mobile Applications",
description: "iOS 与 Android 原生及跨平台应用开发,覆盖全终端用户体验。",
},
{
title: "Web 前端",
subtitle: "Web Development",
description: "响应式网站与单页应用开发,注重用户体验与交互设计。",
},
{
title: "后端架构",
subtitle: "Backend Systems",
description: "高并发后端服务架构设计与数据库优化,保障系统稳定运行。",
},
{
title: "云服务",
subtitle: "Cloud Services",
description: "云原生架构设计与容器化部署方案,实现弹性伸缩与自动化运维。",
},
{
title: "运维支持",
subtitle: "DevOps Support",
description: "7×24小时系统监控与技术支持服务及时响应并解决各类问题。",
},
],
},
footer: {
label: "get in touch",
title: "有项目想法?",
description: "我们随时欢迎有趣的挑战。直接发邮件24小时内回复。不画饼不推销只聊技术。",
email: "system@qczlit.cn",
company: "Yojo",
companyFull: "青春之旅电子信息科技(威海)有限公司",
github: "github",
twitter: "twitter",
blog: "blog",
copyright: "all rights reserved",
},
language: "语言",
}

View File

@@ -1,5 +1,6 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import "./i18n"
import './index.css'
import App from './App.tsx'