Skip to content
Clone
main
250 lines 10.2 KB
import { ArrowRight, GitBranch, GitPullRequest, MessagesSquare, Moon, ShieldCheck, Sun } from 'lucide-react'
import type { LucideIcon } from 'lucide-react'
import { useEffect, useState } from 'react'
import { Navigate, Route, Routes } from 'react-router-dom'
const features: { icon: LucideIcon; title: string; description: string; className: string }[] = [
{
icon: GitBranch,
title: 'Git 仓库',
description: '分支、提交、差异与 LFS 大文件由同一套服务承载,克隆与推送使用标准 Git 协议。',
className: 'md:col-span-4',
},
{
icon: GitPullRequest,
title: '评审与看板',
description: '合并请求、Issue 与看板按空间组织,讨论和变更放在同一处。',
className: 'md:col-span-2',
},
{
icon: MessagesSquare,
title: '团队聊天',
description: '按空间划分的聊天房间,消息与通知直达相关成员。',
className: 'md:col-span-2',
},
{
icon: ShieldCheck,
title: '统一身份',
description: '一个账户登录所有空间,二次验证、SSH 密钥与访问密钥集中管理,随时撤销可疑会话。',
className: 'md:col-span-4',
},
]
function useTheme() {
const [theme, setTheme] = useState(() => {
try {
return localStorage.getItem('auth-theme') || 'system'
} catch {
return 'system'
}
})
const [systemDark, setSystemDark] = useState(() => matchMedia('(prefers-color-scheme: dark)').matches)
const dark = theme === 'dark' || (theme === 'system' && systemDark)
useEffect(() => {
const media = matchMedia('(prefers-color-scheme: dark)')
const update = () => setSystemDark(media.matches)
media.addEventListener('change', update)
return () => media.removeEventListener('change', update)
}, [])
useEffect(() => {
document.documentElement.dataset.theme = dark ? 'dark' : 'light'
}, [dark])
function toggle() {
const next = dark ? 'light' : 'dark'
setTheme(next)
try {
localStorage.setItem('auth-theme', next)
} catch {}
}
return { dark, toggle }
}
function ProductPreview() {
return (
<div className="relative">
<div aria-hidden className="absolute -inset-6 rounded-[32px] bg-accent/10 blur-3xl" />
<div className="relative overflow-hidden rounded-2xl border border-line bg-field shadow-2xl">
<div className="flex items-center gap-2 border-b border-line px-4 py-3">
<span className="size-2.5 rounded-full bg-danger/70" />
<span className="size-2.5 rounded-full bg-accent/70" />
<span className="size-2.5 rounded-full bg-success/70" />
<span className="ml-2 font-latin text-xs text-muted">gitdata.ai / api</span>
</div>
<div className="grid grid-cols-[112px_1fr]">
<aside className="space-y-1 border-r border-line p-3 text-xs">
<p className="mb-2 font-medium text-ink">仓库</p>
<p className="rounded-md bg-page px-2 py-1.5 text-ink">api</p>
<p className="px-2 py-1.5 text-muted">看板</p>
<p className="px-2 py-1.5 text-muted">聊天</p>
<p className="px-2 py-1.5 text-muted">成员</p>
</aside>
<div className="space-y-2 p-4">
<div className="flex items-center gap-2 font-latin text-xs text-muted">
<GitBranch size={12} />
main
</div>
<div className="rounded-lg border border-line bg-page px-3 py-2 font-latin text-xs text-ink">
feat: 统一仓库与看板的访问校验
</div>
<div className="rounded-lg border border-line bg-page px-3 py-2 font-latin text-xs text-muted">
fix: 修正聊天房间通知去重
</div>
<div className="flex items-center gap-1.5 pt-1 text-xs text-success">
<GitPullRequest size={12} />
评审已通过
</div>
</div>
</div>
</div>
</div>
)
}
function FeatureCard({
icon: Icon,
title,
description,
className = '',
}: {
icon: LucideIcon
title: string
description: string
className?: string
}) {
return (
<article className={`landing-card rounded-2xl border border-line bg-field/60 p-6 backdrop-blur ${className}`}>
<span className="mb-4 flex size-10 items-center justify-center rounded-xl border border-line bg-page text-accent">
<Icon size={18} strokeWidth={1.8} />
</span>
<h3 className="mb-2 text-base font-semibold">{title}</h3>
<p className="text-sm leading-[1.7] text-muted">{description}</p>
</article>
)
}
function LandingPage() {
return (
<>
<section className="grid items-center gap-12 py-14 lg:grid-cols-[1.05fr_0.95fr] lg:gap-16 lg:py-24">
<div className="flex flex-col items-start gap-6">
<span className="inline-flex items-center gap-2 rounded-full border border-line bg-field/70 px-3 py-1 text-xs font-medium text-muted backdrop-blur">
<span className="size-1.5 rounded-full bg-accent" />
代码托管 · 评审 · 聊天
</span>
<h1 className="text-[clamp(2.4rem,5vw,4rem)] leading-[1.08] font-semibold tracking-[-1.2px] text-balance">
代码、协作与沟通
<br className="hidden sm:block" />
<span className="bg-gradient-to-r from-accent to-accent-text bg-clip-text text-transparent">
集中在一个平台
</span>
</h1>
<p className="max-w-xl text-base leading-[1.7] text-muted md:text-lg">
GitDataAI 提供 Git 仓库托管、合并请求评审、Issue 看板与团队聊天,并用一套账户打通所有空间与应用的访问权限。
</p>
<div className="mt-1 flex flex-wrap items-center gap-3">
{/* 跨应用整页跳转:/auth 是独立的 MPA 文档,不能走客户端路由。 */}
<a
href="/auth/login"
className="group inline-flex min-h-12 items-center justify-center gap-2 rounded-lg bg-primary px-6 text-base font-semibold text-white shadow-[0_10px_30px_-12px_rgba(240,77,0,0.9)] transition-colors hover:bg-primary-hover active:bg-primary-pressed"
>
登录账户
<ArrowRight size={18} className="transition-transform group-hover:translate-x-0.5" />
</a>
<a
href="/auth/register"
className="inline-flex min-h-12 items-center justify-center rounded-lg border border-control-border bg-field px-6 text-base font-semibold text-ink transition-colors hover:bg-elevated"
>
创建账户
</a>
</div>
</div>
<ProductPreview />
</section>
<section className="pb-20 md:pb-28">
<div className="mb-10 max-w-2xl">
<h2 className="text-2xl font-semibold tracking-[-0.4px] md:text-3xl">从提交到讨论,都在同一个空间里</h2>
<p className="mt-3 text-sm leading-[1.7] text-muted md:text-base">
仓库、评审、看板与聊天按空间组织,权限与身份统一,减少在多个工具之间切换的上下文成本。
</p>
</div>
<div className="grid gap-4 md:grid-cols-6">
{features.map((feature) => (
<FeatureCard
key={feature.title}
icon={feature.icon}
title={feature.title}
description={feature.description}
className={feature.className}
/>
))}
</div>
</section>
</>
)
}
export function LandingApp() {
const { dark, toggle } = useTheme()
const terms = import.meta.env.VITE_TERMS_URL
const privacy = import.meta.env.VITE_PRIVACY_URL
return (
<div className="relative flex min-h-dvh flex-col overflow-hidden bg-page text-ink">
<div aria-hidden className="pointer-events-none absolute inset-x-0 top-0 h-[720px]">
<div className="landing-aurora absolute inset-0" />
<div className="landing-grid absolute inset-0" />
</div>
<header className="relative z-10 mx-auto flex h-16 w-full max-w-6xl items-center justify-between px-5 md:px-10">
<a href="/" className="inline-flex items-center gap-2.75" aria-label="GitDataAI 首页">
<img src="/media/gitdataai-red-panda.png" width={40} height={40} alt="" />
<span className="font-latin text-[19px] font-semibold tracking-[-0.4px]">GitDataAI</span>
</a>
<div className="flex items-center gap-2">
<button
type="button"
onClick={toggle}
aria-label={dark ? '切换到浅色主题' : '切换到深色主题'}
className="flex size-11 items-center justify-center rounded-lg border border-transparent text-icon transition-colors hover:border-line hover:bg-elevated hover:text-ink"
>
{dark ? <Sun size={18} strokeWidth={1.6} /> : <Moon size={18} strokeWidth={1.6} />}
</button>
<a
href="/auth/login"
className="inline-flex min-h-10 items-center rounded-lg border border-control-border bg-field/70 px-4 text-sm font-medium text-ink backdrop-blur transition-colors hover:bg-elevated"
>
登录
</a>
</div>
</header>
<main className="relative z-10 mx-auto w-full max-w-6xl flex-1 px-5 md:px-10">
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</main>
<footer className="relative z-10 border-t border-line/70">
<div className="mx-auto flex w-full max-w-6xl flex-wrap items-center justify-between gap-3 px-5 py-6 text-xs text-muted md:px-10">
<span className="font-latin">© 2026 GitDataAI</span>
{(terms || privacy) && (
<nav className="flex items-center gap-4">
{terms && (
<a href={terms} target="_blank" rel="noopener noreferrer" className="hover:underline">
服务条款
</a>
)}
{privacy && (
<a href={privacy} target="_blank" rel="noopener noreferrer" className="hover:underline">
隐私政策
</a>
)}
</nav>
)}
</div>
</footer>
</div>
)
}