fix: auth button hover, nav links, minimal homepage redesign
Docker Build / Build and Push Docker Image (push) Successful in 4m18s
Docker Build / Build and Push Docker Image (push) Successful in 4m18s
This commit is contained in:
@@ -227,7 +227,7 @@ export function PublicHeader(props: PublicHeaderProps) {
|
||||
tabIndex={link.disabled ? -1 : undefined}
|
||||
onClick={(event) => handleNavLinkClick(event, link)}
|
||||
className={cn(
|
||||
'text-[#8B8D97] hover:text-white rounded-lg px-3 py-1.5 text-[13px] font-medium transition-colors duration-200',
|
||||
'text-[#8B8D97] hover:text-[#C5C6D0] rounded-md px-3 py-1.5 text-[13px] font-medium transition-colors duration-200',
|
||||
link.disabled && 'pointer-events-none opacity-50'
|
||||
)}
|
||||
>
|
||||
@@ -242,14 +242,17 @@ export function PublicHeader(props: PublicHeaderProps) {
|
||||
disabled={link.disabled}
|
||||
onClick={(event) => handleNavLinkClick(event, link)}
|
||||
className={cn(
|
||||
'rounded-lg px-3 py-1.5 text-[13px] font-medium transition-colors duration-200',
|
||||
'relative rounded-md px-3 py-1.5 text-[13px] font-medium transition-colors duration-200',
|
||||
isActive
|
||||
? 'text-white'
|
||||
: 'text-[#8B8D97] hover:text-white',
|
||||
? 'text-[#00D2FF]'
|
||||
: 'text-[#8B8D97] hover:text-[#C5C6D0]',
|
||||
link.disabled && 'pointer-events-none opacity-50'
|
||||
)}
|
||||
>
|
||||
{t(link.title)}
|
||||
{isActive && (
|
||||
<span className='absolute bottom-0 left-3 right-3 h-[2px] rounded-full bg-[#00D2FF]' />
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@ export function ForgotPasswordForm({
|
||||
|
||||
<Button
|
||||
type='submit'
|
||||
className='mt-2 bg-[#00D2FF] text-[#0A0E14] hover:bg-[#00B8E6]'
|
||||
className='mt-2 !bg-[#00D2FF] !text-[#0A0E14] hover:!bg-[#00B8E6]'
|
||||
disabled={isLoading || isActive || !turnstileReady}
|
||||
>
|
||||
{isActive
|
||||
|
||||
@@ -203,7 +203,7 @@ export function OtpForm({ className, ...props }: OtpFormProps) {
|
||||
|
||||
<Button
|
||||
type='submit'
|
||||
className='mt-2 w-full bg-[#00D2FF] text-[#0A0E14] hover:bg-[#00B8E6]'
|
||||
className='mt-2 w-full !bg-[#00D2FF] !text-[#0A0E14] hover:!bg-[#00B8E6]'
|
||||
disabled={!isFormValid || isLoading}
|
||||
>
|
||||
{isLoading ? <Loader2 className='h-4 w-4 animate-spin' /> : null}
|
||||
|
||||
@@ -374,7 +374,7 @@ export function UserAuthForm({
|
||||
{/* Submit Button */}
|
||||
<Button
|
||||
type='submit'
|
||||
className='mt-2 w-full justify-center gap-2 bg-[#00D2FF] text-[#0A0E14] hover:bg-[#00B8E6]'
|
||||
className='mt-2 w-full justify-center gap-2 !bg-[#00D2FF] !text-[#0A0E14] hover:!bg-[#00B8E6]'
|
||||
disabled={isLoading || (requiresLegalConsent && !agreedToLegal)}
|
||||
>
|
||||
{isLoading ? <Loader2 className='animate-spin' /> : <LogIn />}
|
||||
|
||||
@@ -354,7 +354,7 @@ export function SignUpForm({
|
||||
{/* Submit Button */}
|
||||
<Button
|
||||
type='submit'
|
||||
className='mt-2 w-full justify-center gap-2 bg-[#00D2FF] text-[#0A0E14] hover:bg-[#00B8E6]'
|
||||
className='mt-2 w-full justify-center gap-2 !bg-[#00D2FF] !text-[#0A0E14] hover:!bg-[#00B8E6]'
|
||||
disabled={
|
||||
isLoading ||
|
||||
(requiresLegalConsent && !agreedToLegal) ||
|
||||
|
||||
+40
-102
@@ -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 } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStatus } from '@/hooks/use-status'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -27,13 +27,11 @@ interface HeroProps {
|
||||
isAuthenticated?: boolean
|
||||
}
|
||||
|
||||
/* Palette: black bg, cyan accent, gray layers */
|
||||
const T = {
|
||||
bg: '#0A0E14',
|
||||
surface: '#0F1419',
|
||||
border: '#1A1F29',
|
||||
accent: '#00D2FF',
|
||||
accentDim: '#0099BB',
|
||||
green: '#28C840',
|
||||
yellow: '#FEBC2E',
|
||||
gray: '#5C6370',
|
||||
@@ -42,15 +40,6 @@ const T = {
|
||||
red: '#FF5F57',
|
||||
}
|
||||
|
||||
const features = [
|
||||
{ icon: Zap, title: 'Multi-model Routing', desc: 'Intelligent routing with automatic failover and load balancing across providers' },
|
||||
{ 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()
|
||||
@@ -60,28 +49,22 @@ export function Hero(props: HeroProps) {
|
||||
|
||||
return (
|
||||
<section className='relative overflow-hidden' style={{ backgroundColor: T.bg }}>
|
||||
{/* Top glow */}
|
||||
{/* Subtle top glow */}
|
||||
<div
|
||||
aria-hidden
|
||||
className='pointer-events-none absolute inset-0'
|
||||
style={{
|
||||
background:
|
||||
'radial-gradient(ellipse 50% 30% at 50% 0%, rgba(0,210,255,0.07) 0%, transparent 70%)',
|
||||
'radial-gradient(ellipse 40% 25% at 50% 0%, rgba(0,210,255,0.06) 0%, transparent 70%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className='relative mx-auto max-w-5xl px-6'>
|
||||
<div className='relative mx-auto max-w-4xl px-6'>
|
||||
{/* ── Hero ── */}
|
||||
<div className='flex min-h-[calc(100svh-3rem)] flex-col items-center justify-center py-20 text-center'>
|
||||
{/* Eyebrow */}
|
||||
<div className='mb-8 inline-flex items-center gap-2 rounded-full border px-3.5 py-1 text-xs' style={{ borderColor: T.border, backgroundColor: T.surface, color: T.grayLight }}>
|
||||
<span className='inline-block size-1.5 rounded-full' style={{ backgroundColor: T.accent }} />
|
||||
{t('Compatible with OpenAI API')}
|
||||
</div>
|
||||
|
||||
{/* Headline */}
|
||||
<h1
|
||||
className='max-w-2xl text-[clamp(2.2rem,6vw,4rem)] leading-[1.05] font-bold tracking-[-0.03em]'
|
||||
className='max-w-xl text-[clamp(2.4rem,7vw,4.5rem)] leading-[1] font-bold tracking-[-0.04em]'
|
||||
style={{ color: T.white }}
|
||||
>
|
||||
{t('Unified LLM')}{' '}
|
||||
@@ -89,12 +72,12 @@ export function Hero(props: HeroProps) {
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
<p className='mt-6 max-w-md text-base leading-relaxed' style={{ color: T.grayLight }}>
|
||||
<p className='mt-6 max-w-sm text-[15px] leading-relaxed' style={{ color: T.grayLight }}>
|
||||
{t('One endpoint for all models. OpenAI-compatible, switch and go.')}
|
||||
</p>
|
||||
|
||||
{/* CTA */}
|
||||
<div className='mt-10 flex flex-wrap items-center justify-center gap-3'>
|
||||
<div className='mt-10 flex items-center gap-4'>
|
||||
{props.isAuthenticated ? (
|
||||
<Button
|
||||
className='group h-11 rounded-lg px-7 text-sm font-semibold hover:opacity-90'
|
||||
@@ -114,123 +97,78 @@ export function Hero(props: HeroProps) {
|
||||
{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 px-7 text-sm font-medium hover:opacity-90'
|
||||
style={{ borderColor: T.border, backgroundColor: 'transparent', color: T.white }}
|
||||
render={<Link to='/pricing' />}
|
||||
<Link
|
||||
to='/pricing'
|
||||
className='text-[13px] font-medium transition-colors duration-200'
|
||||
style={{ color: T.grayLight }}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.color = T.accent }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.color = T.grayLight }}
|
||||
>
|
||||
{t('View Pricing')}
|
||||
</Button>
|
||||
{t('View Pricing')} →
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Terminal demo ── */}
|
||||
<div className='-mt-8 pb-20'>
|
||||
<div className='-mt-4 pb-28'>
|
||||
<div
|
||||
className='mx-auto w-full max-w-2xl overflow-hidden rounded-xl border shadow-[0_16px_64px_-16px_rgba(0,0,0,0.5)]'
|
||||
className='mx-auto w-full max-w-xl overflow-hidden rounded-xl border'
|
||||
style={{ borderColor: T.border, backgroundColor: T.surface }}
|
||||
>
|
||||
{/* Terminal header */}
|
||||
<div className='flex items-center gap-2 border-b px-4 py-2.5' style={{ borderColor: T.border }}>
|
||||
<div className='flex items-center gap-2 border-b px-4 py-2' style={{ borderColor: T.border }}>
|
||||
<div className='flex gap-1.5'>
|
||||
<div className='size-2.5 rounded-full' style={{ backgroundColor: T.red }} />
|
||||
<div className='size-2.5 rounded-full' style={{ backgroundColor: T.yellow }} />
|
||||
<div className='size-2.5 rounded-full' style={{ backgroundColor: T.green }} />
|
||||
<div className='size-2 rounded-full' style={{ backgroundColor: T.red }} />
|
||||
<div className='size-2 rounded-full' style={{ backgroundColor: T.yellow }} />
|
||||
<div className='size-2 rounded-full' style={{ backgroundColor: T.green }} />
|
||||
</div>
|
||||
<span className='ml-2 text-[11px]' style={{ color: T.gray }}>bash</span>
|
||||
<span className='ml-2 text-[10px]' style={{ color: T.gray }}>bash</span>
|
||||
</div>
|
||||
{/* Terminal content */}
|
||||
<div className='p-5 font-mono text-[13px] leading-[1.9]'>
|
||||
<div className='p-4 font-mono text-[12px] leading-[2]'>
|
||||
<div>
|
||||
<span style={{ color: T.accent }}>$</span>{' '}
|
||||
<span style={{ color: T.white }}>curl</span>{' '}
|
||||
<span style={{ color: T.accent }}>{serverAddress}/v1/chat/completions</span>{' '}
|
||||
<span style={{ color: T.gray }}>\</span>
|
||||
</div>
|
||||
<div className='pl-5'>
|
||||
<div className='pl-4'>
|
||||
<span style={{ color: T.gray }}>-H</span>{' '}
|
||||
<span style={{ color: T.yellow }}>"Authorization: Bearer sk-..."</span>{' '}
|
||||
<span style={{ color: T.gray }}>\</span>
|
||||
</div>
|
||||
<div className='pl-5'>
|
||||
<div className='pl-4'>
|
||||
<span style={{ color: T.gray }}>-d</span>{' '}
|
||||
<span style={{ color: T.yellow }}>{"'{ \"model\": \"gpt-4o\", \"messages\": [...] }'"}</span>
|
||||
<span style={{ color: T.yellow }}>{"'{ \"model\": \"gpt-4o\" }'"}</span>
|
||||
</div>
|
||||
|
||||
<div className='mt-5 border-t pt-4' style={{ borderColor: T.border }}>
|
||||
<div className='flex items-center gap-2 mb-3'>
|
||||
<div className='mt-4 border-t pt-3' style={{ borderColor: T.border }}>
|
||||
<div className='flex items-center gap-2 mb-2'>
|
||||
<span className='inline-block size-1.5 rounded-full' style={{ backgroundColor: T.green }} />
|
||||
<span className='text-[11px] font-medium' style={{ color: T.gray }}>200 OK</span>
|
||||
<span className='text-[11px]' style={{ color: T.gray }}>312ms</span>
|
||||
<span className='text-[10px] font-medium' style={{ color: T.gray }}>200</span>
|
||||
<span className='text-[10px]' style={{ color: T.gray }}>312ms</span>
|
||||
</div>
|
||||
<div>
|
||||
<span style={{ color: T.gray }}>{"{"}</span>{' '}
|
||||
<span style={{ color: T.accent }}>"model"</span>
|
||||
<span style={{ color: T.gray }}>:</span>{' '}
|
||||
<span style={{ color: T.yellow }}>"gpt-4o"</span>
|
||||
<span style={{ color: T.gray }}>,</span>{' '}
|
||||
<span style={{ color: T.accent }}>"usage"</span>
|
||||
<span style={{ color: T.gray }}>:</span>{' '}
|
||||
<span style={{ color: T.gray }}>{"{ \"prompt_tokens\": 12, \"completion_tokens\": 47 }"}</span>
|
||||
<span style={{ color: T.gray }}>{"{"}</span>
|
||||
<span style={{ color: T.accent }}> "model"</span>
|
||||
<span style={{ color: T.gray }}>:</span>
|
||||
<span style={{ color: T.yellow }}> "gpt-4o"</span>
|
||||
<span style={{ color: T.gray }}>,</span>
|
||||
<span style={{ color: T.accent }}> "usage"</span>
|
||||
<span style={{ color: T.gray }}>:</span>
|
||||
<span style={{ color: T.gray }}> {"{...}"} </span>
|
||||
<span style={{ color: T.gray }}>{"}"}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-3'>
|
||||
<div className='mt-2'>
|
||||
<span style={{ color: T.accent }}>$</span>
|
||||
<span className='ml-1 inline-block h-3.5 w-[7px] animate-pulse align-middle' style={{ backgroundColor: T.accent }} />
|
||||
<span className='ml-1 inline-block h-3 w-[6px] animate-pulse align-middle' style={{ backgroundColor: T.accent }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Features ── */}
|
||||
<div className='pb-28'>
|
||||
<div className='mb-14 text-center'>
|
||||
<h2 className='text-2xl font-semibold tracking-tight' style={{ color: T.white }}>
|
||||
{t('Full-featured gateway')}
|
||||
</h2>
|
||||
<p className='mt-3 text-sm' style={{ color: T.grayLight }}>
|
||||
{t('Everything you need to manage, route, and monitor LLM API traffic')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='grid gap-5 sm:grid-cols-2 lg:grid-cols-3'>
|
||||
{features.map((feature) => (
|
||||
<div
|
||||
key={feature.title}
|
||||
className='group rounded-xl border p-6 transition-all duration-200'
|
||||
style={{
|
||||
borderColor: T.border,
|
||||
backgroundColor: T.surface,
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.borderColor = T.accentDim
|
||||
e.currentTarget.style.transform = 'translateY(-2px)'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.borderColor = T.border
|
||||
e.currentTarget.style.transform = 'translateY(0)'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className='mb-4 flex size-10 items-center justify-center rounded-lg'
|
||||
style={{ backgroundColor: `${T.accent}12` }}
|
||||
>
|
||||
<feature.icon className='size-5' style={{ color: T.accent }} />
|
||||
</div>
|
||||
<h3 className='text-sm font-semibold' style={{ color: T.white }}>
|
||||
{t(feature.title)}
|
||||
</h3>
|
||||
<p className='mt-2 text-[13px] leading-relaxed' style={{ color: T.gray }}>
|
||||
{t(feature.desc)}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user