fix: light mode button colors, redesign homepage with features grid
Docker Build / Build and Push Docker Image (push) Successful in 4m13s
Docker Build / Build and Push Docker Image (push) Successful in 4m13s
This commit is contained in:
+2
-2
@@ -34,7 +34,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
|
||||
export function LanguageSwitcher() {
|
||||
export function LanguageSwitcher({ className }: { className?: string }) {
|
||||
const { i18n, t } = useTranslation()
|
||||
const user = useAuthStore((s) => s.auth.user)
|
||||
const currentLanguage = normalizeInterfaceLanguage(i18n.language)
|
||||
@@ -56,7 +56,7 @@ export function LanguageSwitcher() {
|
||||
return (
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
render={<Button variant='ghost' size='icon' className='h-9 w-9' />}
|
||||
render={<Button variant='ghost' size='icon' className={cn('h-9 w-9', className)} />}
|
||||
>
|
||||
<Languages className='size-[1.2rem]' />
|
||||
<span className='sr-only'>{t('Change language')}</span>
|
||||
|
||||
@@ -257,13 +257,14 @@ export function PublicHeader(props: PublicHeaderProps) {
|
||||
{(showLanguageSwitcher ||
|
||||
showThemeSwitch ||
|
||||
showNotifications) && (
|
||||
<div className='bg-border/40 mx-2 h-4 w-px' />
|
||||
<div className='mx-2 h-4 w-px bg-white/10' />
|
||||
)}
|
||||
|
||||
{showLanguageSwitcher && <LanguageSwitcher />}
|
||||
{showThemeSwitch && <ThemeSwitch />}
|
||||
{showLanguageSwitcher && <LanguageSwitcher className='text-white/70 hover:text-white' />}
|
||||
{showThemeSwitch && <ThemeSwitch className='text-white/70 hover:text-white' />}
|
||||
{showNotifications && (
|
||||
<NotificationPopover
|
||||
className='text-white/70 hover:text-white'
|
||||
open={notifications.popoverOpen}
|
||||
onOpenChange={notifications.setPopoverOpen}
|
||||
unreadCount={notifications.unreadCount}
|
||||
@@ -277,7 +278,7 @@ export function PublicHeader(props: PublicHeaderProps) {
|
||||
|
||||
{showAuthButtons && (
|
||||
<>
|
||||
<div className='bg-border/40 mx-1 h-4 w-px' />
|
||||
<div className='mx-1 h-4 w-px bg-white/10' />
|
||||
{loading ? (
|
||||
<Skeleton className='h-8 w-20 rounded-lg' />
|
||||
) : isAuthenticated ? (
|
||||
@@ -297,7 +298,7 @@ export function PublicHeader(props: PublicHeaderProps) {
|
||||
|
||||
{/* Mobile: compact actions + hamburger */}
|
||||
<div className='flex items-center gap-2 sm:hidden'>
|
||||
{showThemeSwitch && <ThemeSwitch />}
|
||||
{showThemeSwitch && <ThemeSwitch className='text-white/70 hover:text-white' />}
|
||||
{showAuthButtons && !loading && isAuthenticated && (
|
||||
<ProfileDropdown />
|
||||
)}
|
||||
|
||||
+2
-2
@@ -29,7 +29,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
|
||||
export function ThemeSwitch() {
|
||||
export function ThemeSwitch({ className }: { className?: string }) {
|
||||
const { t } = useTranslation()
|
||||
const { theme, setTheme } = useTheme()
|
||||
|
||||
@@ -44,7 +44,7 @@ export function ThemeSwitch() {
|
||||
return (
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
render={<Button variant='ghost' size='icon' className='h-9 w-9' />}
|
||||
render={<Button variant='ghost' size='icon' className={cn('h-9 w-9', className)} />}
|
||||
>
|
||||
<Sun className='size-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90' />
|
||||
<Moon className='absolute size-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0' />
|
||||
|
||||
+88
-45
@@ -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 } from 'lucide-react'
|
||||
import { ArrowRight, Zap, Shield, Layers, BarChart3, Key, GitBranch } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStatus } from '@/hooks/use-status'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -27,6 +27,15 @@ interface HeroProps {
|
||||
isAuthenticated?: boolean
|
||||
}
|
||||
|
||||
const features = [
|
||||
{ icon: Zap, title: 'Multi-model Routing', desc: 'Intelligent routing across providers with automatic failover and load balancing' },
|
||||
{ icon: Key, title: 'Key Management', desc: 'Centralized API key lifecycle management with usage tracking and rotation' },
|
||||
{ icon: Shield, title: 'Access Control', desc: 'Fine-grained permissions, rate limiting, and token-level access policies' },
|
||||
{ icon: BarChart3, title: 'Real-time Monitoring', desc: 'Live dashboards with request latency, token usage, and cost analytics' },
|
||||
{ icon: Layers, title: 'OpenAI Compatible', desc: 'Drop-in replacement for OpenAI API — no code changes required' },
|
||||
{ icon: GitBranch, title: 'Model Mapping', desc: 'Map model names, override parameters, and customize per-channel behavior' },
|
||||
]
|
||||
|
||||
export function Hero(props: HeroProps) {
|
||||
const { t } = useTranslation()
|
||||
const { status } = useStatus()
|
||||
@@ -36,68 +45,71 @@ export function Hero(props: HeroProps) {
|
||||
|
||||
return (
|
||||
<section className='relative overflow-hidden bg-[#0C0D10]'>
|
||||
{/* Subtle gradient glow */}
|
||||
{/* Top gradient glow */}
|
||||
<div
|
||||
aria-hidden
|
||||
className='pointer-events-none absolute inset-0'
|
||||
style={{
|
||||
background:
|
||||
'radial-gradient(ellipse 80% 50% at 50% 0%, rgba(0,210,255,0.06) 0%, transparent 60%)',
|
||||
'radial-gradient(ellipse 70% 40% at 50% -5%, rgba(0,210,255,0.08) 0%, transparent 70%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className='relative mx-auto flex min-h-[calc(100svh-3rem)] max-w-5xl flex-col items-center justify-center px-6 py-24 text-center'>
|
||||
{/* Badge */}
|
||||
<div className='mb-6 inline-flex items-center gap-2 rounded-full border border-[#2A2B33] bg-[#13141A] px-4 py-1.5 text-xs text-[#8B8D97]'>
|
||||
<span className='inline-block size-1.5 rounded-full bg-[#00D2FF]' />
|
||||
{t('Compatible with OpenAI API')}
|
||||
</div>
|
||||
<div className='relative mx-auto max-w-6xl px-6'>
|
||||
{/* Hero area */}
|
||||
<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-[#2A2B33] bg-[#13141A] px-4 py-1.5 text-xs text-[#8B8D97]'>
|
||||
<span className='inline-block size-1.5 rounded-full bg-[#00D2FF]' />
|
||||
{t('Compatible with OpenAI API')}
|
||||
</div>
|
||||
|
||||
{/* Headline */}
|
||||
<h1 className='max-w-3xl text-[clamp(2rem,5.5vw,3.5rem)] leading-[1.1] font-bold tracking-[-0.02em] text-white'>
|
||||
{t('Unified LLM Gateway')}
|
||||
</h1>
|
||||
{/* Headline */}
|
||||
<h1 className='max-w-3xl text-[clamp(2rem,5.5vw,3.5rem)] leading-[1.1] font-bold tracking-[-0.025em] text-white'>
|
||||
{t('Unified LLM Gateway')}
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
<p className='mt-5 max-w-lg text-base leading-relaxed text-[#8B8D97] sm:text-lg'>
|
||||
{t(
|
||||
'One endpoint for all models. OpenAI-compatible, switch and go.'
|
||||
)}
|
||||
</p>
|
||||
{/* Subtitle */}
|
||||
<p className='mt-5 max-w-lg text-base leading-relaxed text-[#8B8D97] sm:text-lg'>
|
||||
{t(
|
||||
'One endpoint for all models. OpenAI-compatible, switch and go.'
|
||||
)}
|
||||
</p>
|
||||
|
||||
{/* CTA */}
|
||||
<div className='mt-8 flex flex-wrap items-center justify-center gap-3'>
|
||||
{props.isAuthenticated ? (
|
||||
<Button
|
||||
className='group h-11 rounded-lg bg-[#00D2FF] px-6 text-sm font-semibold text-[#0C0D10] hover:bg-[#00B8E6]'
|
||||
render={<Link to='/dashboard' />}
|
||||
>
|
||||
{t('Go to Dashboard')}
|
||||
<ArrowRight className='ml-1.5 size-4 transition-transform duration-200 group-hover:translate-x-0.5' />
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
{/* CTA */}
|
||||
<div className='mt-8 flex flex-wrap items-center justify-center gap-3'>
|
||||
{props.isAuthenticated ? (
|
||||
<Button
|
||||
className='group h-11 rounded-lg bg-[#00D2FF] px-6 text-sm font-semibold text-[#0C0D10] hover:bg-[#00B8E6]'
|
||||
render={<Link to='/sign-up' />}
|
||||
render={<Link to='/dashboard' />}
|
||||
>
|
||||
{t('Get Started')}
|
||||
{t('Go to Dashboard')}
|
||||
<ArrowRight className='ml-1.5 size-4 transition-transform duration-200 group-hover:translate-x-0.5' />
|
||||
</Button>
|
||||
<Button
|
||||
variant='outline'
|
||||
className='h-11 rounded-lg border-[#2A2B33] bg-transparent px-6 text-sm font-medium text-[#C5C6D0] hover:border-[#3A3B45] hover:bg-[#1A1B20] hover:text-white'
|
||||
render={<Link to='/pricing' />}
|
||||
>
|
||||
{t('View Pricing')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
className='group h-11 rounded-lg bg-[#00D2FF] px-6 text-sm font-semibold text-[#0C0D10] hover:bg-[#00B8E6]'
|
||||
render={<Link to='/sign-up' />}
|
||||
>
|
||||
{t('Get Started')}
|
||||
<ArrowRight className='ml-1.5 size-4 transition-transform duration-200 group-hover:translate-x-0.5' />
|
||||
</Button>
|
||||
<Button
|
||||
variant='outline'
|
||||
className='h-11 rounded-lg border-[#2A2B33] bg-transparent px-6 text-sm font-medium text-[#C5C6D0] hover:border-[#3A3B45] hover:bg-[#1A1B20] hover:text-white'
|
||||
render={<Link to='/pricing' />}
|
||||
>
|
||||
{t('View Pricing')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Terminal demo */}
|
||||
<div className='mt-20 w-full max-w-2xl'>
|
||||
<div className='overflow-hidden rounded-lg border border-[#2A2B33] bg-[#13141A] shadow-[0_8px_40px_-12px_rgba(0,0,0,0.6)]'>
|
||||
{/* Terminal demo - centered, overlapping hero and features */}
|
||||
<div className='-mt-10 mb-20 flex justify-center pb-8'>
|
||||
<div className='w-full max-w-2xl overflow-hidden rounded-lg border border-[#2A2B33] bg-[#13141A] shadow-[0_8px_40px_-12px_rgba(0,0,0,0.6)]'>
|
||||
{/* Terminal header */}
|
||||
<div className='flex items-center gap-2 border-b border-[#2A2B33] px-4 py-3'>
|
||||
<div className='flex gap-1.5'>
|
||||
@@ -149,6 +161,37 @@ export function Hero(props: HeroProps) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features grid */}
|
||||
<div className='pb-24'>
|
||||
<div className='mb-12 text-center'>
|
||||
<h2 className='text-xl font-semibold tracking-tight text-white sm:text-2xl'>
|
||||
{t('Full-featured gateway')}
|
||||
</h2>
|
||||
<p className='mt-2 text-sm text-[#8B8D97]'>
|
||||
{t('Everything you need to manage, route, and monitor LLM API traffic')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='grid gap-4 sm:grid-cols-2 lg:grid-cols-3'>
|
||||
{features.map((feature) => (
|
||||
<div
|
||||
key={feature.title}
|
||||
className='group rounded-lg border border-[#2A2B33] bg-[#13141A] p-5 transition-colors duration-200 hover:border-[#3A3B45]'
|
||||
>
|
||||
<div className='mb-3 flex size-9 items-center justify-center rounded-md bg-[#00D2FF]/10'>
|
||||
<feature.icon className='size-[18px] text-[#00D2FF]' />
|
||||
</div>
|
||||
<h3 className='text-sm font-semibold text-[#C5C6D0]'>
|
||||
{t(feature.title)}
|
||||
</h3>
|
||||
<p className='mt-1.5 text-[13px] leading-relaxed text-[#5C5D66]'>
|
||||
{t(feature.desc)}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
Vendored
+13
@@ -940,6 +940,19 @@
|
||||
"Full-featured gateway": "Full-featured gateway",
|
||||
"Key management": "Key management",
|
||||
"Multi-model routing": "Multi-model routing",
|
||||
"Multi-model Routing": "Multi-model Routing",
|
||||
"Intelligent routing across providers with automatic failover and load balancing": "Intelligent routing across providers with automatic failover and load balancing",
|
||||
"Key Management": "Key Management",
|
||||
"Centralized API key lifecycle management with usage tracking and rotation": "Centralized API key lifecycle management with usage tracking and rotation",
|
||||
"Access Control": "Access Control",
|
||||
"Fine-grained permissions, rate limiting, and token-level access policies": "Fine-grained permissions, rate limiting, and token-level access policies",
|
||||
"Real-time Monitoring": "Real-time Monitoring",
|
||||
"Live dashboards with request latency, token usage, and cost analytics": "Live dashboards with request latency, token usage, and cost analytics",
|
||||
"OpenAI Compatible": "OpenAI Compatible",
|
||||
"Drop-in replacement for OpenAI API — no code changes required": "Drop-in replacement for OpenAI API — no code changes required",
|
||||
"Model Mapping": "Model Mapping",
|
||||
"Map model names, override parameters, and customize per-channel behavior": "Map model names, override parameters, and customize per-channel behavior",
|
||||
"Everything you need to manage, route, and monitor LLM API traffic": "Everything you need to manage, route, and monitor LLM API traffic",
|
||||
"No more re-entering API keys. Everything is saved, so you connect instantly.": "No more re-entering API keys. Everything is saved, so you connect instantly.",
|
||||
"Real-time monitoring": "Real-time monitoring",
|
||||
"Ready to get started?": "Ready to get started?",
|
||||
|
||||
Vendored
+13
@@ -940,6 +940,19 @@
|
||||
"Full-featured gateway": "全功能网关",
|
||||
"Key management": "密钥管理",
|
||||
"Multi-model routing": "多模型路由",
|
||||
"Multi-model Routing": "多模型路由",
|
||||
"Intelligent routing across providers with automatic failover and load balancing": "跨供应商智能路由,自动故障转移与负载均衡",
|
||||
"Key Management": "密钥管理",
|
||||
"Centralized API key lifecycle management with usage tracking and rotation": "集中管理 API 密钥生命周期,追踪用量与轮换",
|
||||
"Access Control": "访问控制",
|
||||
"Fine-grained permissions, rate limiting, and token-level access policies": "细粒度权限、速率限制与令牌级访问策略",
|
||||
"Real-time Monitoring": "实时监控",
|
||||
"Live dashboards with request latency, token usage, and cost analytics": "实时仪表盘,监控请求延迟、令牌用量与成本分析",
|
||||
"OpenAI Compatible": "兼容 OpenAI",
|
||||
"Drop-in replacement for OpenAI API — no code changes required": "直接替换 OpenAI API,无需修改代码",
|
||||
"Model Mapping": "模型映射",
|
||||
"Map model names, override parameters, and customize per-channel behavior": "映射模型名称、覆盖参数、自定义渠道行为",
|
||||
"Everything you need to manage, route, and monitor LLM API traffic": "管理、路由和监控大模型 API 流量所需的一切",
|
||||
"No more re-entering API keys. Everything is saved, so you connect instantly.": "无需重复输入 API 密钥,一切已保存,即刻连接。",
|
||||
"Real-time monitoring": "实时监控",
|
||||
"Ready to get started?": "准备开始了吗?",
|
||||
|
||||
Reference in New Issue
Block a user