redesign: homepage layout referencing company project structure
Docker Build / Build and Push Docker Image (push) Successful in 4m19s

This commit is contained in:
2026-06-14 18:44:58 +08:00
parent ca47fd18f5
commit 8eb31a7c82
3 changed files with 216 additions and 125 deletions
+208 -125
View File
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact admin@modelstoken.com
*/
import { Link } from '@tanstack/react-router'
import { ArrowRight, Zap, Shield, Layers, BarChart3, Key, GitBranch } from 'lucide-react'
import { ArrowRight, Zap, Shield, Layers, BarChart3, Key, GitBranch, Users } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { useStatus } from '@/hooks/use-status'
import { Button } from '@/components/ui/button'
@@ -32,8 +32,11 @@ const features = [
{ icon: Key, title: 'Key Management', desc: 'Centralized API key lifecycle with usage tracking' },
{ icon: Shield, title: 'Access Control', desc: 'Fine-grained permissions, rate limits, and token policies' },
{ icon: BarChart3, title: 'Real-time Monitoring', desc: 'Live dashboards with latency and cost analytics' },
{ icon: Layers, title: 'OpenAI Compatible', desc: 'Drop-in replacement with zero code changes needed' },
{ icon: GitBranch, title: 'Model Mapping', desc: 'Custom model names and per-channel behavior' },
]
const providers = [
'OpenAI', 'Claude', 'Gemini', 'Mistral', 'DeepSeek',
'Llama', 'Qwen', 'Cohere', 'Groq', 'Perplexity',
]
export function Hero(props: HeroProps) {
@@ -48,10 +51,10 @@ export function Hero(props: HeroProps) {
{/* Subtle dot grid */}
<div
aria-hidden
className='pointer-events-none absolute inset-0 opacity-[0.035] dark:opacity-[0.04]'
className='pointer-events-none absolute inset-0 opacity-[0.03] dark:opacity-[0.04]'
style={{
backgroundImage:
'radial-gradient(circle, var(--ring) 0.5px, transparent 0.5px)',
'radial-gradient(circle, var(--primary) 0.5px, transparent 0.5px)',
backgroundSize: '24px 24px',
}}
/>
@@ -66,38 +69,200 @@ export function Hero(props: HeroProps) {
}}
/>
<div className='relative mx-auto max-w-5xl px-6'>
{/* ── Hero ── */}
<div className='flex min-h-[calc(100svh-3rem)] flex-col items-center justify-center py-20 text-center'>
{/* Badge */}
<div className='mb-6 inline-flex items-center gap-2 rounded-full border border-border bg-card px-3.5 py-1.5 text-xs font-medium text-muted-foreground'>
<span className='inline-block size-1.5 rounded-full bg-[oklch(0.65_0.17_210)] animate-pulse' />
{t('OpenAI-compatible API gateway')}
<div className='relative mx-auto max-w-6xl px-6'>
{/* ── Hero: left text + right terminal ── */}
<div className='flex min-h-[calc(100svh-3rem)] flex-col items-center justify-center gap-12 py-20 lg:flex-row lg:items-center lg:gap-16'>
{/* Left: text content */}
<div className='flex-1 text-center lg:text-left'>
<h1 className='text-[clamp(2rem,5vw,3.5rem)] leading-[1.1] font-bold tracking-[-0.025em] text-foreground'>
{t('One endpoint')}{' '}
<span className='text-primary'>{t('for every model')}</span>
</h1>
<p className='mt-4 max-w-md text-[15px] leading-relaxed text-muted-foreground lg:max-w-none'>
{t('Route, monitor, and manage LLM traffic through a single gateway. Switch providers in seconds.')}
</p>
{/* CTA */}
<div className='mt-8 flex items-center gap-3 justify-center lg:justify-start'>
{props.isAuthenticated ? (
<Button
className='group h-10 rounded-lg px-6 text-sm font-semibold'
render={<Link to='/dashboard' />}
>
{t('Dashboard')}
<ArrowRight className='ml-1 size-3.5 transition-transform duration-200 group-hover:translate-x-0.5' />
</Button>
) : (
<>
<Button
className='group h-10 rounded-lg px-6 text-sm font-semibold'
render={<Link to='/sign-up' />}
>
{t('Get Started')}
<ArrowRight className='ml-1 size-3.5 transition-transform duration-200 group-hover:translate-x-0.5' />
</Button>
<Link
to='/pricing'
className='h-10 inline-flex items-center rounded-lg px-4 text-sm font-medium text-muted-foreground transition-colors duration-200 hover:text-primary'
>
{t('Pricing')}
</Link>
</>
)}
</div>
{/* Social proof */}
<div className='mt-6 flex items-center gap-2 justify-center lg:justify-start'>
<div className='flex -space-x-2'>
{Array.from({ length: 4 }).map((_, i) => (
<div
key={i}
className='flex size-7 items-center justify-center rounded-full border-2 border-background bg-muted text-[10px] font-bold text-muted-foreground'
>
{String.fromCharCode(65 + i)}
</div>
))}
</div>
<span className='text-xs text-muted-foreground'>
<span className='font-semibold text-foreground'>1K+</span> {t('developers joined')}
</span>
</div>
</div>
{/* Headline */}
<h1 className='max-w-xl text-[clamp(2rem,5.5vw,3.8rem)] leading-[1.08] font-bold tracking-[-0.025em] text-foreground'>
{t('One endpoint')}{' '}
<span className='text-[oklch(0.65_0.17_210)] dark:text-primary'>{t('for every model')}</span>
</h1>
{/* Right: terminal demo */}
<div className='w-full max-w-xl flex-1 lg:max-w-none'>
<div className='overflow-hidden rounded-lg border border-border bg-card shadow-sm dark:shadow-[0_0_60px_-16px_oklch(0.65_0.17_210/0.12)]'>
{/* Title bar */}
<div className='flex items-center gap-2 border-b border-border px-4 py-2'>
<div className='flex gap-1.5'>
<div className='size-[9px] rounded-full bg-foreground/15 dark:bg-foreground/20' />
<div className='size-[9px] rounded-full bg-foreground/15 dark:bg-foreground/20' />
<div className='size-[9px] rounded-full bg-foreground/15 dark:bg-foreground/20' />
</div>
<span className='ml-1.5 text-[11px] font-medium text-muted-foreground/70'>terminal</span>
</div>
{/* Content */}
<div className='p-5 font-mono text-[13px] leading-[1.9]'>
<div>
<span className='text-primary'>$</span>{' '}
<span className='text-foreground'>curl</span>{' '}
<span className='text-primary'>{serverAddress}/v1/chat/completions</span>{' '}
<span className='text-muted-foreground'>\</span>
</div>
<div className='pl-4'>
<span className='text-muted-foreground'>-H</span>{' '}
<span className='text-foreground/70'>{'"Authorization: Bearer sk-..."'}</span>{' '}
<span className='text-muted-foreground'>\</span>
</div>
<div className='pl-4'>
<span className='text-muted-foreground'>-d</span>{' '}
<span className='text-foreground/70'>{"'{ \"model\": \"gpt-4o\", \"messages\": [...] }'"}</span>
</div>
{/* Subtitle */}
<p className='mt-4 max-w-md text-[15px] leading-relaxed text-muted-foreground'>
{t('Route, monitor, and manage LLM traffic through a single gateway. Switch providers in seconds.')}
<div className='mt-4 border-t border-border pt-3'>
<div className='mb-2 flex items-center gap-2'>
<span className='inline-block size-1.5 rounded-full bg-green-500' />
<span className='text-[11px] font-medium text-muted-foreground'>200 OK</span>
<span className='text-[11px] text-muted-foreground/60'>312ms</span>
</div>
<div>
<span className='text-muted-foreground'>{"{"}</span>
<span className='text-primary'> "model"</span>
<span className='text-muted-foreground'>:</span>
<span className='text-foreground/70'> "gpt-4o"</span>
<span className='text-muted-foreground'>,</span>
<span className='text-primary'> "usage"</span>
<span className='text-muted-foreground'>:</span>
<span className='text-muted-foreground'> {"{ \"prompt_tokens\": 12, \"completion_tokens\": 47 }"}</span>
<span className='text-muted-foreground'>{"}"}</span>
</div>
</div>
<div className='mt-2'>
<span className='text-primary'>$</span>
<span className='ml-1 inline-block h-3.5 w-[6px] animate-pulse bg-primary align-middle' />
</div>
</div>
</div>
</div>
</div>
{/* ── Provider logos marquee ── */}
<div className='relative -mt-8 pb-20'>
<p className='mb-4 text-center text-xs font-medium uppercase tracking-widest text-muted-foreground/60'>
{t('Supported providers')}
</p>
<div className='relative overflow-hidden'>
<div className='flex animate-[marquee_30s_linear_infinite] gap-8'>
{[...providers, ...providers].map((name, i) => (
<span
key={`${name}-${i}`}
className='shrink-0 text-sm font-semibold text-muted-foreground/40'
>
{name}
</span>
))}
</div>
{/* Fade edges */}
<div className='pointer-events-none absolute inset-y-0 left-0 w-16 bg-gradient-to-r from-background to-transparent' />
<div className='pointer-events-none absolute inset-y-0 right-0 w-16 bg-gradient-to-l from-background to-transparent' />
</div>
</div>
{/* CTA */}
<div className='mt-8 flex items-center gap-3'>
{props.isAuthenticated ? (
<Button
className='group h-10 rounded-lg px-6 text-sm font-semibold'
render={<Link to='/dashboard' />}
{/* ── Features (2x2 grid like company) ── */}
<div className='pb-20'>
<div className='mb-10 text-center'>
<h2 className='text-2xl font-bold tracking-tight text-foreground'>
{t('Features')}
</h2>
<p className='mt-2 text-sm text-muted-foreground'>
{t('Everything you need to manage and route LLM API traffic')}
</p>
</div>
<div className='grid gap-4 md:grid-cols-2'>
{features.map((feature) => (
<div
key={feature.title}
className='group relative overflow-hidden rounded-xl border border-border bg-card p-6 transition-colors duration-200 hover:border-primary/30'
>
{t('Dashboard')}
<ArrowRight className='ml-1 size-3.5 transition-transform duration-200 group-hover:translate-x-0.5' />
</Button>
) : (
<>
{/* Glow effect on hover */}
<div className='pointer-events-none absolute -inset-px rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100' style={{ background: 'radial-gradient(200px circle at var(--mouse-x, 50%) var(--mouse-y, 50%), oklch(0.65 0.17 210 / 0.06), transparent 60%)' }} />
<div className='relative'>
<div className='mb-4 flex size-10 items-center justify-center rounded-lg border border-border bg-background'>
<feature.icon className='size-5 text-primary' />
</div>
<h3 className='text-base font-semibold text-foreground'>
{t(feature.title)}
</h3>
<p className='mt-2 text-sm leading-relaxed text-muted-foreground'>
{t(feature.desc)}
</p>
</div>
</div>
))}
</div>
</div>
{/* ── Setup / CTA section ── */}
<div className='pb-24'>
<div className='relative flex flex-col items-center justify-center overflow-hidden rounded-xl border border-border bg-card py-16 text-center'>
<h2 className='text-2xl font-bold tracking-tight text-foreground'>
{t('Ready to get started?')}
</h2>
<p className='mt-2 text-sm text-muted-foreground'>
{t('Just 3 minutes to start using')}
</p>
<div className='mt-6 flex items-center gap-3'>
{props.isAuthenticated ? (
<Button
className='group h-10 rounded-lg px-6 text-sm font-semibold'
render={<Link to='/dashboard' />}
>
{t('Dashboard')}
<ArrowRight className='ml-1 size-3.5 transition-transform duration-200 group-hover:translate-x-0.5' />
</Button>
) : (
<Button
className='group h-10 rounded-lg px-6 text-sm font-semibold'
render={<Link to='/sign-up' />}
@@ -105,104 +270,22 @@ export function Hero(props: HeroProps) {
{t('Get Started')}
<ArrowRight className='ml-1 size-3.5 transition-transform duration-200 group-hover:translate-x-0.5' />
</Button>
<Link
to='/pricing'
className='h-10 inline-flex items-center rounded-lg px-4 text-sm font-medium text-muted-foreground transition-colors duration-200 hover:text-[oklch(0.65_0.17_210)] dark:hover:text-primary'
>
{t('Pricing')}
</Link>
</>
)}
</div>
</div>
{/* ── Terminal ── */}
<div className='-mt-4 pb-20'>
<div className='mx-auto w-full max-w-2xl overflow-hidden rounded-lg border border-border bg-card shadow-sm dark:shadow-[0_0_60px_-16px_oklch(0.65_0.17_210/0.12)]'>
{/* Title bar */}
<div className='flex items-center gap-2 border-b border-border px-4 py-2'>
<div className='flex gap-1.5'>
<div className='size-[9px] rounded-full bg-foreground/15 dark:bg-foreground/20' />
<div className='size-[9px] rounded-full bg-foreground/15 dark:bg-foreground/20' />
<div className='size-[9px] rounded-full bg-foreground/15 dark:bg-foreground/20' />
</div>
<span className='ml-1.5 text-[11px] font-medium text-muted-foreground/70'>terminal</span>
)}
</div>
{/* Content */}
<div className='p-5 font-mono text-[13px] leading-[1.9]'>
<div>
<span className='text-[oklch(0.65_0.17_210)] dark:text-primary'>$</span>{' '}
<span className='text-foreground'>curl</span>{' '}
<span className='text-[oklch(0.65_0.17_210)] dark:text-primary'>{serverAddress}/v1/chat/completions</span>{' '}
<span className='text-muted-foreground'>\</span>
</div>
<div className='pl-4'>
<span className='text-muted-foreground'>-H</span>{' '}
<span className='text-foreground/70'>{'"Authorization: Bearer sk-..."'}</span>{' '}
<span className='text-muted-foreground'>\</span>
</div>
<div className='pl-4'>
<span className='text-muted-foreground'>-d</span>{' '}
<span className='text-foreground/70'>{"'{ \"model\": \"gpt-4o\", \"messages\": [...] }'"}</span>
</div>
<div className='mt-4 border-t border-border pt-3'>
<div className='mb-2 flex items-center gap-2'>
<span className='inline-block size-1.5 rounded-full bg-green-500' />
<span className='text-[11px] font-medium text-muted-foreground'>200 OK</span>
<span className='text-[11px] text-muted-foreground/60'>312ms</span>
</div>
<div>
<span className='text-muted-foreground'>{"{"}</span>
<span className='text-[oklch(0.65_0.17_210)] dark:text-primary'> "model"</span>
<span className='text-muted-foreground'>:</span>
<span className='text-foreground/70'> "gpt-4o"</span>
<span className='text-muted-foreground'>,</span>
<span className='text-[oklch(0.65_0.17_210)] dark:text-primary'> "usage"</span>
<span className='text-muted-foreground'>:</span>
<span className='text-muted-foreground'> {"{ \"prompt_tokens\": 12, \"completion_tokens\": 47 }"}</span>
<span className='text-muted-foreground'>{"}"}</span>
</div>
</div>
<div className='mt-2'>
<span className='text-[oklch(0.65_0.17_210)] dark:text-primary'>$</span>
<span className='ml-1 inline-block h-3.5 w-[6px] animate-pulse bg-[oklch(0.65_0.17_210)] dark:bg-primary align-middle' />
</div>
</div>
</div>
</div>
{/* ── Features ── */}
<div className='pb-24'>
<div className='mb-12 text-center'>
<h2 className='text-lg font-semibold tracking-tight text-foreground'>
{t('Built for production')}
</h2>
<p className='mt-1.5 text-sm text-muted-foreground'>
{t('Everything you need to manage and route LLM API traffic')}
</p>
</div>
<div className='grid gap-3 sm:grid-cols-2 lg:grid-cols-3'>
{features.map((feature) => (
<div
key={feature.title}
className='group rounded-lg border border-border bg-card p-5 transition-colors duration-200 hover:border-[oklch(0.65_0.17_210/0.3)] dark:hover:border-primary/30'
>
<div className='mb-3 flex size-8 items-center justify-center rounded-md bg-[oklch(0.65_0.17_210/0.08)] dark:bg-primary/8'>
<feature.icon className='size-4 text-[oklch(0.65_0.17_210)] dark:text-primary' />
</div>
<h3 className='text-[13px] font-semibold text-foreground'>
{t(feature.title)}
</h3>
<p className='mt-1 text-[13px] leading-relaxed text-muted-foreground'>
{t(feature.desc)}
</p>
</div>
))}
{/* Decorative circles */}
<div aria-hidden className='pointer-events-none absolute -top-20 -right-20 size-60 rounded-full bg-primary/5 blur-3xl' />
<div aria-hidden className='pointer-events-none absolute -bottom-20 -left-20 size-60 rounded-full bg-primary/5 blur-3xl' />
</div>
</div>
</div>
{/* Marquee keyframes */}
<style>{`
@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
`}</style>
</section>
)
}
+4
View File
@@ -973,6 +973,10 @@
"Live dashboards with latency and cost analytics": "Live dashboards with latency and cost analytics",
"Drop-in replacement with zero code changes needed": "Drop-in replacement with zero code changes needed",
"Custom model names and per-channel behavior": "Custom model names and per-channel behavior",
"developers joined": "developers joined",
"Supported providers": "Supported providers",
"Features": "Features",
"Just 3 minutes to start using": "Just 3 minutes to start using",
"Copied {{count}} key(s)": "Copied {{count}} key(s)",
"Copied to clipboard": "Copied to clipboard",
"Copied: {{model}}": "Copied: {{model}}",
+4
View File
@@ -973,6 +973,10 @@
"Live dashboards with latency and cost analytics": "实时仪表板,延迟与成本分析",
"Drop-in replacement with zero code changes needed": "零代码改动即可替换的即插即用方案",
"Custom model names and per-channel behavior": "自定义模型名称与按渠道行为配置",
"developers joined": "位开发者已加入",
"Supported providers": "支持的供应商",
"Features": "功能特性",
"Just 3 minutes to start using": "只需 3 分钟即可开始使用",
"Copied {{count}} key(s)": "已复制 {{count}} 个密钥",
"Copied to clipboard": "已复制到剪贴板",
"Copied: {{model}}": "已复制: {{model}}",