redesign: homepage layout referencing company project structure
Docker Build / Build and Push Docker Image (push) Successful in 4m19s
Docker Build / Build and Push Docker Image (push) Successful in 4m19s
This commit is contained in:
+208
-125
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Vendored
+4
@@ -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}}",
|
||||
|
||||
Vendored
+4
@@ -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}}",
|
||||
|
||||
Reference in New Issue
Block a user