feat: add language switch button and optimize theme visuals
- Add LanguageSwitch component to all 4 theme layouts (starter/default/dark/minimal) - Import Noto Sans SC for proper Chinese font rendering - Fix theme flash on refresh by blocking render until site info loads - Optimize default theme: violet/blue/cyan palette, SVG icons instead of emoji - Optimize dark theme: unified emerald accent, reduced particle count, backdrop-blur - Add Tailwind safelist for dark theme dynamic class names - Prioritize localStorage in i18n detection so manual language choice persists Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
9a18cf2215
commit
662685cc97
43 files changed
+420
-316
No files matched your search
@@ -0,0 +1,25 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function LanguageSwitch({ className = '' }) {
|
||||
const { i18n } = useTranslation();
|
||||
const isZh = i18n.language?.startsWith('zh');
|
||||
|
||||
const toggle = () => {
|
||||
i18n.changeLanguage(isZh ? 'en' : 'zh');
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggle}
|
||||
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs transition-colors ${className}`}
|
||||
aria-label="Switch language"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3.6 9h16.8M3.6 15h16.8" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3a15.3 15.3 0 0 1 4 9 15.3 15.3 0 0 1-4 9 15.3 15.3 0 0 1-4-9 15.3 15.3 0 0 1 4-9Z" />
|
||||
</svg>
|
||||
<span>{isZh ? 'EN' : '中文'}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -23,10 +23,35 @@ const themeRegistry = {
|
||||
},
|
||||
};
|
||||
|
||||
// Full-screen loading spinner shown while site info is being fetched
|
||||
// Prevents theme flash (rendering default theme before API returns the real one)
|
||||
function ThemeLoading() {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen bg-gray-950">
|
||||
<div className="w-8 h-8 border-2 border-white/20 border-t-white/70 rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ThemeProvider({ children }) {
|
||||
const { site } = useSite();
|
||||
const { site, loading } = useSite();
|
||||
|
||||
// Block rendering until site info is loaded so we know the correct theme
|
||||
if (loading) {
|
||||
return <ThemeLoading />;
|
||||
}
|
||||
|
||||
const themeName = site?.theme_template || 'starter';
|
||||
|
||||
return (
|
||||
<ThemeInner themeName={themeName}>
|
||||
{children}
|
||||
</ThemeInner>
|
||||
);
|
||||
}
|
||||
|
||||
// Inner component to keep useMemo stable after loading completes
|
||||
function ThemeInner({ themeName, children }) {
|
||||
const theme = useMemo(() => {
|
||||
const t = themeRegistry[themeName] || themeRegistry.starter;
|
||||
return { name: themeName, ...t };
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ i18n
|
||||
fallbackLng: 'en',
|
||||
interpolation: { escapeValue: false },
|
||||
detection: {
|
||||
order: ['navigator', 'htmlTag', 'querystring'],
|
||||
order: ['localStorage', 'navigator', 'htmlTag', 'querystring'],
|
||||
caches: ['localStorage'],
|
||||
},
|
||||
});
|
||||
|
||||
+64
-49
@@ -27,10 +27,10 @@ export default function DarkHome() {
|
||||
{/* Particles Background */}
|
||||
<div className="absolute inset-0 h-[800px] overflow-hidden pointer-events-none">
|
||||
<Particles
|
||||
particleColors={['#00ff88', '#00ccff', '#ff0055']}
|
||||
particleCount={80}
|
||||
particleColors={['#10b981', '#06b6d4', '#8b5cf6']}
|
||||
particleCount={60}
|
||||
particleSpread={15}
|
||||
speed={0.3}
|
||||
speed={0.2}
|
||||
particleBaseSize={80}
|
||||
moveParticlesOnHover={true}
|
||||
alphaParticles={false}
|
||||
@@ -41,38 +41,39 @@ export default function DarkHome() {
|
||||
{/* Hero Section */}
|
||||
<section className="relative max-w-7xl mx-auto px-6 pt-28 pb-24">
|
||||
<div className="text-center max-w-4xl mx-auto">
|
||||
{/* Terminal-style header */}
|
||||
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-green-500/30 bg-green-500/5 mb-8">
|
||||
<span className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
|
||||
<span className="text-xs font-mono text-green-400 uppercase tracking-wider">System Online</span>
|
||||
{/* Terminal badge */}
|
||||
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-emerald-500/20 bg-emerald-500/5 mb-8">
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
||||
<span className="text-xs font-mono text-emerald-400/80 uppercase tracking-wider">System Online</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl md:text-7xl font-heading font-extrabold text-white leading-tight mb-4">
|
||||
<h1 className="text-5xl md:text-7xl font-extrabold text-white leading-tight mb-6 tracking-tight">
|
||||
<DecryptedText
|
||||
text={site?.name || t('home.defaultHeroTitle')}
|
||||
speed={60}
|
||||
maxIterations={10}
|
||||
revealDirection="start"
|
||||
className="text-white"
|
||||
encryptedClassName="text-green-500/40"
|
||||
encryptedClassName="text-emerald-500/30"
|
||||
/>
|
||||
</h1>
|
||||
|
||||
<p className="text-lg md:text-xl text-neutral-400 max-w-2xl mx-auto mb-10 font-mono">
|
||||
{'> '}{t('home.heroSubtitle')}
|
||||
<p className="text-lg md:text-xl text-neutral-400 max-w-2xl mx-auto mb-10 leading-relaxed">
|
||||
<span className="text-emerald-400/60 font-mono">{'> '}</span>
|
||||
{t('home.heroSubtitle')}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
{user ? (
|
||||
<Link to="/dashboard" className="px-8 py-3.5 rounded-lg bg-green-500 text-black font-bold text-base hover:bg-green-400 transition-all font-mono shadow-lg shadow-green-500/20">
|
||||
<Link to="/dashboard" className="px-8 py-3.5 rounded-lg bg-emerald-500 text-black font-bold text-base hover:bg-emerald-400 transition-all font-mono shadow-lg shadow-emerald-500/20">
|
||||
{t('home.goToDashboard')}
|
||||
</Link>
|
||||
) : (
|
||||
<>
|
||||
<Link to="/register" className="px-8 py-3.5 rounded-lg bg-green-500 text-black font-bold text-base hover:bg-green-400 transition-all font-mono shadow-lg shadow-green-500/20">
|
||||
<Link to="/register" className="px-8 py-3.5 rounded-lg bg-emerald-500 text-black font-bold text-base hover:bg-emerald-400 transition-all font-mono shadow-lg shadow-emerald-500/20">
|
||||
{t('home.getStarted')}
|
||||
</Link>
|
||||
<Link to="/pricing" className="px-8 py-3.5 rounded-lg border border-green-500/30 text-green-400 font-bold text-base hover:bg-green-500/10 transition-all font-mono">
|
||||
<Link to="/pricing" className="px-8 py-3.5 rounded-lg border border-emerald-500/20 text-emerald-400 font-medium text-base hover:bg-emerald-500/10 transition-all font-mono">
|
||||
{t('home.viewPricing')}
|
||||
</Link>
|
||||
</>
|
||||
@@ -80,18 +81,18 @@ export default function DarkHome() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats — terminal style */}
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-3 gap-4 max-w-2xl mx-auto mt-20">
|
||||
{[
|
||||
{ label: t('home.aiModels'), value: enabledModels.length || 50, suffix: '+', color: 'text-green-400 border-green-500/20' },
|
||||
{ label: t('home.uptime'), value: 99.9, suffix: '%', color: 'text-cyan-400 border-cyan-500/20' },
|
||||
{ label: t('home.latency'), value: 50, suffix: 'ms', prefix: '<', color: 'text-pink-400 border-pink-500/20' },
|
||||
{ label: t('home.aiModels'), value: enabledModels.length || 50, suffix: '+', color: 'text-emerald-400', border: 'border-emerald-500/15' },
|
||||
{ label: t('home.uptime'), value: 99.9, suffix: '%', color: 'text-cyan-400', border: 'border-cyan-500/15' },
|
||||
{ label: t('home.latency'), value: 50, suffix: 'ms', prefix: '<', color: 'text-violet-400', border: 'border-violet-500/15' },
|
||||
].map((s, i) => (
|
||||
<div key={i} className={`text-center p-4 rounded-lg bg-black/60 border ${s.color} font-mono`}>
|
||||
<div className={`text-3xl font-bold ${s.color.split(' ')[0]}`}>
|
||||
<div key={i} className={`text-center p-5 rounded-lg bg-[#030712]/60 border ${s.border} font-mono backdrop-blur-sm`}>
|
||||
<div className={`text-3xl font-bold ${s.color}`}>
|
||||
{s.prefix}<CountUp from={0} to={s.value} duration={2} />{s.suffix}
|
||||
</div>
|
||||
<p className="text-xs text-neutral-500 mt-1 uppercase tracking-wider">{s.label}</p>
|
||||
<p className="text-xs text-neutral-500 mt-1.5 uppercase tracking-wider">{s.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -100,21 +101,35 @@ export default function DarkHome() {
|
||||
{/* Features Section */}
|
||||
<section className="relative max-w-7xl mx-auto px-6 py-20">
|
||||
<div className="text-center mb-14">
|
||||
<h2 className="text-3xl font-heading font-bold text-white mb-3">{t('home.whyChooseUs')}</h2>
|
||||
<p className="text-neutral-500 font-mono">{t('home.whyChooseUsDesc')}</p>
|
||||
<h2 className="text-3xl font-bold text-white mb-3">{t('home.whyChooseUs')}</h2>
|
||||
<p className="text-neutral-500">{t('home.whyChooseUsDesc')}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{[
|
||||
{ icon: '⚡', title: t('home.lightningFast'), desc: t('home.lightningFastDesc'), accent: 'green' },
|
||||
{ icon: '🛡️', title: t('home.securePrivate'), desc: t('home.securePrivateDesc'), accent: 'cyan' },
|
||||
{ icon: '📊', title: t('home.payAsYouGo'), desc: t('home.payAsYouGoDesc'), accent: 'pink' },
|
||||
{ icon: (
|
||||
<svg className="w-5 h-5 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
), title: t('home.lightningFast'), desc: t('home.lightningFastDesc'), accent: 'emerald' },
|
||||
{ icon: (
|
||||
<svg className="w-5 h-5 text-cyan-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
), title: t('home.securePrivate'), desc: t('home.securePrivateDesc'), accent: 'cyan' },
|
||||
{ icon: (
|
||||
<svg className="w-5 h-5 text-violet-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
), title: t('home.payAsYouGo'), desc: t('home.payAsYouGoDesc'), accent: 'violet' },
|
||||
].map((f, i) => (
|
||||
<div key={i} className={`p-6 rounded-xl bg-black/40 border border-${f.accent}-500/20 hover:border-${f.accent}-500/40 transition-all group`}>
|
||||
<div className="text-2xl mb-3">{f.icon}</div>
|
||||
<h3 className={`text-lg font-semibold text-${f.accent}-400 mb-2 font-mono`}>{f.title}</h3>
|
||||
<div key={i} className={`p-6 rounded-xl bg-[#030712]/60 border border-${f.accent}-500/10 hover:border-${f.accent}-500/25 transition-all group backdrop-blur-sm`}>
|
||||
<div className={`w-10 h-10 rounded-lg bg-${f.accent}-500/10 flex items-center justify-center mb-4`}>
|
||||
{f.icon}
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-white mb-2">{f.title}</h3>
|
||||
<p className="text-sm text-neutral-400 leading-relaxed">{f.desc}</p>
|
||||
<div className={`h-0.5 w-0 group-hover:w-full bg-${f.accent}-500/50 transition-all duration-500 mt-4`} />
|
||||
<div className={`h-px w-0 group-hover:w-full bg-gradient-to-r from-${f.accent}-500/50 to-transparent transition-all duration-500 mt-4`} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -124,15 +139,15 @@ export default function DarkHome() {
|
||||
{enabledModels.length > 0 && (
|
||||
<section className="relative max-w-7xl mx-auto px-6 py-20">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-heading font-bold text-white mb-3">{t('home.availableModels')}</h2>
|
||||
<p className="text-neutral-500 font-mono">{t('home.availableModelsDesc', { count: enabledModels.length })}</p>
|
||||
<h2 className="text-3xl font-bold text-white mb-3">{t('home.availableModels')}</h2>
|
||||
<p className="text-neutral-500">{t('home.availableModelsDesc', { count: enabledModels.length })}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2">
|
||||
{enabledModels.slice(0, 12).map((m, i) => (
|
||||
<div key={m.id || i} className="px-4 py-3 rounded-lg bg-black/40 border border-green-500/10 flex items-center gap-3 hover:border-green-500/30 transition-colors group font-mono">
|
||||
<span className="text-green-500 text-xs">$</span>
|
||||
<span className="text-sm text-neutral-400 group-hover:text-green-400 transition-colors truncate">
|
||||
<div key={m.id || i} className="px-4 py-3 rounded-lg bg-[#030712]/60 border border-emerald-500/[0.08] flex items-center gap-3 hover:border-emerald-500/20 transition-colors group font-mono backdrop-blur-sm">
|
||||
<span className="text-emerald-500/60 text-xs">$</span>
|
||||
<span className="text-sm text-neutral-400 group-hover:text-emerald-400 transition-colors truncate">
|
||||
{m.display_name || m.model_name}
|
||||
</span>
|
||||
</div>
|
||||
@@ -141,7 +156,7 @@ export default function DarkHome() {
|
||||
|
||||
{enabledModels.length > 12 && (
|
||||
<div className="text-center mt-6">
|
||||
<Link to="/pricing" className="text-sm text-green-400 hover:text-green-300 transition-colors font-mono">
|
||||
<Link to="/pricing" className="text-sm text-emerald-400 hover:text-emerald-300 transition-colors font-mono">
|
||||
{t('home.viewAllModels', { count: enabledModels.length })} →
|
||||
</Link>
|
||||
</div>
|
||||
@@ -153,23 +168,23 @@ export default function DarkHome() {
|
||||
{packages.length > 0 && (
|
||||
<section className="relative max-w-7xl mx-auto px-6 py-20">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-heading font-bold text-white mb-3">{t('home.plansPackages')}</h2>
|
||||
<p className="text-neutral-500 font-mono">{t('home.choosePlan')}</p>
|
||||
<h2 className="text-3xl font-bold text-white mb-3">{t('home.plansPackages')}</h2>
|
||||
<p className="text-neutral-500">{t('home.choosePlan')}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6 max-w-4xl mx-auto">
|
||||
{packages.filter(p => p.enabled).slice(0, 3).map((pkg) => (
|
||||
<div key={pkg.id} className="rounded-xl p-6 flex flex-col bg-black/40 border border-green-500/10 hover:border-green-500/30 transition-all">
|
||||
<h3 className="text-lg font-semibold text-white mb-1 font-mono">{pkg.name}</h3>
|
||||
<div key={pkg.id} className="rounded-xl p-6 flex flex-col bg-[#030712]/60 border border-emerald-500/[0.08] hover:border-emerald-500/20 transition-all backdrop-blur-sm">
|
||||
<h3 className="text-lg font-semibold text-white mb-1">{pkg.name}</h3>
|
||||
{pkg.description && <p className="text-sm text-neutral-400 mb-4">{pkg.description}</p>}
|
||||
<div className="mt-auto pt-4">
|
||||
<span className="text-3xl font-bold text-green-400 font-mono">${pkg.price}</span>
|
||||
<span className="text-3xl font-bold text-emerald-400 font-mono">${pkg.price}</span>
|
||||
{pkg.original_price > pkg.price && (
|
||||
<span className="text-sm text-neutral-600 line-through ml-2">${pkg.original_price}</span>
|
||||
)}
|
||||
{pkg.duration > 0 && <p className="text-xs text-neutral-600 mt-1 font-mono">{t('home.days', { count: pkg.duration })}</p>}
|
||||
{pkg.duration > 0 && <p className="text-xs text-neutral-600 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
||||
</div>
|
||||
<Link to={user ? '/packages' : '/register'} className="mt-4 py-2.5 rounded-lg bg-green-500/10 border border-green-500/30 text-green-400 font-medium text-sm text-center hover:bg-green-500/20 transition-colors font-mono">
|
||||
<Link to={user ? '/packages' : '/register'} className="mt-4 py-2.5 rounded-lg bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 font-medium text-sm text-center hover:bg-emerald-500/15 transition-colors font-mono">
|
||||
{user ? t('home.subscribe') : t('home.getStarted')}
|
||||
</Link>
|
||||
</div>
|
||||
@@ -180,22 +195,22 @@ export default function DarkHome() {
|
||||
|
||||
{/* CTA */}
|
||||
<section className="relative max-w-7xl mx-auto px-6 py-24">
|
||||
<div className="rounded-xl p-12 text-center bg-black/60 border border-green-500/20 relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-green-500/5 via-transparent to-cyan-500/5" />
|
||||
<div className="rounded-xl p-12 text-center bg-[#030712]/60 border border-emerald-500/[0.08] relative overflow-hidden backdrop-blur-sm">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-emerald-500/[0.03] via-transparent to-cyan-500/[0.03]" />
|
||||
<div className="relative z-10">
|
||||
<h2 className="text-3xl font-heading font-bold text-white mb-4 font-mono">{t('home.readyToStart')}</h2>
|
||||
<h2 className="text-3xl font-bold text-white mb-4">{t('home.readyToStart')}</h2>
|
||||
<p className="text-neutral-400 mb-8 max-w-md mx-auto">{t('home.readyToStartDesc')}</p>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
{user ? (
|
||||
<Link to="/dashboard" className="px-8 py-3 rounded-lg bg-green-500 text-black font-bold hover:bg-green-400 transition-all font-mono">
|
||||
<Link to="/dashboard" className="px-8 py-3 rounded-lg bg-emerald-500 text-black font-bold hover:bg-emerald-400 transition-all font-mono">
|
||||
{t('home.goToDashboard')}
|
||||
</Link>
|
||||
) : (
|
||||
<>
|
||||
<Link to="/register" className="px-8 py-3 rounded-lg bg-green-500 text-black font-bold hover:bg-green-400 transition-all font-mono">
|
||||
<Link to="/register" className="px-8 py-3 rounded-lg bg-emerald-500 text-black font-bold hover:bg-emerald-400 transition-all font-mono">
|
||||
{t('home.createFreeAccount')}
|
||||
</Link>
|
||||
<Link to="/login" className="px-8 py-3 rounded-lg border border-green-500/30 text-green-400 font-bold hover:bg-green-500/10 transition-all font-mono">
|
||||
<Link to="/login" className="px-8 py-3 rounded-lg border border-emerald-500/20 text-emerald-400 font-medium hover:bg-emerald-500/10 transition-all font-mono">
|
||||
{t('home.signIn')}
|
||||
</Link>
|
||||
</>
|
||||
|
||||
+21
-18
@@ -3,6 +3,7 @@ import { Outlet, Link, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useSite } from '../../context/SiteContext';
|
||||
import LanguageSwitch from '../../components/LanguageSwitch';
|
||||
|
||||
export default function DarkLayout() {
|
||||
const { t } = useTranslation();
|
||||
@@ -25,26 +26,26 @@ export default function DarkLayout() {
|
||||
const visibleNavItems = navItems.filter((n) => !n.auth || user);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-black text-white">
|
||||
<div className="min-h-screen flex flex-col bg-[#030712] text-white">
|
||||
{/* Announcement Banner */}
|
||||
{site?.announcement && (
|
||||
<div className="bg-green-500/10 border-b border-green-500/20 text-center py-2 px-4">
|
||||
<p className="text-sm text-green-300 font-mono">{site.announcement}</p>
|
||||
<div className="bg-emerald-500/5 border-b border-emerald-500/10 text-center py-2 px-4">
|
||||
<p className="text-sm text-emerald-300/80 font-mono">{site.announcement}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Header — cyberpunk style */}
|
||||
<header className="sticky top-0 z-50 backdrop-blur-xl bg-black/80 border-b border-green-500/10">
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-50 backdrop-blur-xl bg-[#030712]/80 border-b border-emerald-500/[0.08]">
|
||||
<div className="max-w-7xl mx-auto px-6 h-14 flex items-center justify-between">
|
||||
<Link to="/" className="flex items-center gap-3 group">
|
||||
{site?.logo ? (
|
||||
<img src={site.logo} alt={siteName} className="h-7 w-auto" />
|
||||
) : (
|
||||
<div className="w-8 h-8 rounded-md bg-green-500/20 border border-green-500/30 flex items-center justify-center text-green-400 font-bold font-mono text-sm">
|
||||
<div className="w-8 h-8 rounded bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400 font-bold font-mono text-sm">
|
||||
{siteName.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
<span className="text-lg font-semibold text-green-400 font-mono group-hover:text-green-300 transition-colors">
|
||||
<span className="text-base font-semibold text-emerald-400 font-mono group-hover:text-emerald-300 transition-colors tracking-wide">
|
||||
{siteName}
|
||||
</span>
|
||||
</Link>
|
||||
@@ -56,8 +57,8 @@ export default function DarkLayout() {
|
||||
to={n.to}
|
||||
className={`px-3 py-1.5 text-sm font-mono rounded transition-all ${
|
||||
location.pathname === n.to
|
||||
? 'text-green-400 bg-green-500/10'
|
||||
: 'text-neutral-500 hover:text-green-400'
|
||||
? 'text-emerald-400 bg-emerald-500/10'
|
||||
: 'text-neutral-500 hover:text-emerald-400'
|
||||
}`}
|
||||
>
|
||||
{n.label}
|
||||
@@ -65,26 +66,28 @@ export default function DarkLayout() {
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<LanguageSwitch className="text-neutral-500 hover:text-emerald-400 hover:bg-emerald-500/5 font-mono" />
|
||||
|
||||
{user ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-neutral-500 font-mono hidden sm:block">{user.display_name || user.username}</span>
|
||||
<button onClick={async () => { await logout(); navigate('/'); }} className="text-sm text-neutral-500 hover:text-green-400 font-mono transition-colors">
|
||||
<button onClick={async () => { await logout(); navigate('/'); }} className="text-sm text-neutral-500 hover:text-emerald-400 font-mono transition-colors">
|
||||
{t('nav.logout')}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Link to="/login" className="text-sm text-neutral-500 hover:text-green-400 font-mono transition-colors hidden sm:block">
|
||||
<Link to="/login" className="text-sm text-neutral-500 hover:text-emerald-400 font-mono transition-colors hidden sm:block px-3 py-1.5">
|
||||
{t('nav.login')}
|
||||
</Link>
|
||||
<Link to="/register" className="px-4 py-1.5 rounded-md bg-green-500/10 border border-green-500/30 text-green-400 text-sm font-mono hover:bg-green-500/20 transition-all">
|
||||
<Link to="/register" className="px-4 py-1.5 rounded bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-sm font-mono hover:bg-emerald-500/15 transition-all">
|
||||
{t('nav.signUp')}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
<button className="md:hidden p-1.5 rounded hover:bg-white/5" onClick={() => setMobileMenuOpen(!mobileMenuOpen)}>
|
||||
<svg className="w-5 h-5 text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg className="w-5 h-5 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
{mobileMenuOpen ? <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> : <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />}
|
||||
</svg>
|
||||
</button>
|
||||
@@ -92,11 +95,11 @@ export default function DarkLayout() {
|
||||
</div>
|
||||
|
||||
{mobileMenuOpen && (
|
||||
<div className="md:hidden border-t border-green-500/10 bg-black/95 backdrop-blur-xl">
|
||||
<div className="md:hidden border-t border-emerald-500/[0.08] bg-[#030712]/95 backdrop-blur-xl">
|
||||
<nav className="max-w-7xl mx-auto px-6 py-3 flex flex-col gap-1">
|
||||
{visibleNavItems.map((n) => (
|
||||
<Link key={n.to} to={n.to} onClick={() => setMobileMenuOpen(false)}
|
||||
className={`px-3 py-2 text-sm font-mono rounded transition-colors ${location.pathname === n.to ? 'text-green-400 bg-green-500/10' : 'text-neutral-500 hover:text-green-400'}`}>
|
||||
className={`px-3 py-2 text-sm font-mono rounded transition-colors ${location.pathname === n.to ? 'text-emerald-400 bg-emerald-500/10' : 'text-neutral-500 hover:text-emerald-400'}`}>
|
||||
{n.label}
|
||||
</Link>
|
||||
))}
|
||||
@@ -107,11 +110,11 @@ export default function DarkLayout() {
|
||||
|
||||
<main className="flex-1"><Outlet /></main>
|
||||
|
||||
<footer className="border-t border-green-500/10 mt-auto">
|
||||
<footer className="border-t border-emerald-500/[0.08] mt-auto">
|
||||
<div className="max-w-7xl mx-auto px-6 py-6 flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
<p className="text-xs text-neutral-600 font-mono">© {new Date().getFullYear()} {siteName}</p>
|
||||
{site?.contact_email && (
|
||||
<a href={`mailto:${site.contact_email}`} className="text-xs text-neutral-600 hover:text-green-400 font-mono transition-colors">
|
||||
<a href={`mailto:${site.contact_email}`} className="text-xs text-neutral-600 hover:text-emerald-400 font-mono transition-colors">
|
||||
{t('nav.contact')}
|
||||
</a>
|
||||
)}
|
||||
|
||||
+51
-32
@@ -27,33 +27,33 @@ export default function DefaultHome() {
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* Aurora Background */}
|
||||
<div className="absolute inset-0 h-[700px] overflow-hidden pointer-events-none">
|
||||
<Aurora colorStops={["#3A29FF", "#FF94B4", "#FF3232"]} blend={0.5} amplitude={1.0} speed={0.5} />
|
||||
<div className="absolute inset-0 h-[700px] overflow-hidden pointer-events-none opacity-60">
|
||||
<Aurora colorStops={["#4338ca", "#7c3aed", "#06b6d4"]} blend={0.5} amplitude={1.0} speed={0.5} />
|
||||
</div>
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative max-w-7xl mx-auto px-6 pt-28 pb-24">
|
||||
<div className="text-center max-w-4xl mx-auto">
|
||||
<div className="mb-4">
|
||||
<h1 className="text-5xl md:text-7xl font-heading font-extrabold text-white leading-tight">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-5xl md:text-7xl font-extrabold text-white leading-tight tracking-tight">
|
||||
<SplitText text={site?.name || t('home.defaultHeroTitle')} delay={80} className="inline" />
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-2 text-xl md:text-2xl text-neutral-300 mb-10">
|
||||
<span>{t('home.heroSubtitle').split('.')[0]}.</span>
|
||||
</div>
|
||||
<p className="text-lg md:text-xl text-neutral-400 max-w-2xl mx-auto mb-4 leading-relaxed">
|
||||
{t('home.heroSubtitle')}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap items-center justify-center gap-3 mb-6">
|
||||
<div className="flex flex-wrap items-center justify-center gap-3 mb-10">
|
||||
<RotatingText
|
||||
texts={[t('home.lightningFast'), t('home.securePrivate'), t('home.payAsYouGo')]}
|
||||
mainClassName="px-4 py-2 bg-white/10 backdrop-blur-sm rounded-full text-white text-sm font-medium overflow-hidden"
|
||||
mainClassName="px-4 py-2 bg-white/[0.06] backdrop-blur-sm rounded-full text-white text-sm font-medium overflow-hidden border border-white/[0.08]"
|
||||
staggerFrom="last"
|
||||
rotationInterval={3000}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-4 mt-8">
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
{user ? (
|
||||
<Link to="/dashboard" className="px-8 py-3.5 rounded-2xl bg-white text-black font-semibold text-base hover:bg-neutral-100 transition-all shadow-xl shadow-white/10">
|
||||
{t('home.goToDashboard')}
|
||||
@@ -63,7 +63,7 @@ export default function DefaultHome() {
|
||||
<Link to="/register" className="px-8 py-3.5 rounded-2xl bg-white text-black font-semibold text-base hover:bg-neutral-100 transition-all shadow-xl shadow-white/10">
|
||||
{t('home.getStarted')}
|
||||
</Link>
|
||||
<Link to="/pricing" className="px-8 py-3.5 rounded-2xl border border-white/20 text-white font-semibold text-base hover:bg-white/10 transition-all">
|
||||
<Link to="/pricing" className="px-8 py-3.5 rounded-2xl border border-white/20 text-white font-medium text-base hover:bg-white/10 transition-all">
|
||||
{t('home.viewPricing')}
|
||||
</Link>
|
||||
</>
|
||||
@@ -72,13 +72,13 @@ export default function DefaultHome() {
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-3 gap-8 max-w-2xl mx-auto mt-24">
|
||||
<div className="grid grid-cols-3 gap-6 max-w-2xl mx-auto mt-24">
|
||||
{[
|
||||
{ value: enabledModels.length || 50, suffix: '+', label: t('home.aiModels') },
|
||||
{ value: 99.9, suffix: '%', label: t('home.uptime') },
|
||||
{ value: 50, suffix: 'ms', label: t('home.latency'), prefix: '<' },
|
||||
].map((stat, i) => (
|
||||
<div key={i} className="text-center p-4 rounded-2xl bg-white/5 backdrop-blur-sm border border-white/10">
|
||||
<div key={i} className="text-center p-5 rounded-2xl bg-white/[0.04] backdrop-blur-sm border border-white/[0.08]">
|
||||
<div className="text-3xl font-bold text-white">
|
||||
{stat.prefix}<CountUp from={0} to={stat.value} duration={2} />{stat.suffix}
|
||||
</div>
|
||||
@@ -91,27 +91,41 @@ export default function DefaultHome() {
|
||||
{/* Features Section */}
|
||||
<section className="relative max-w-7xl mx-auto px-6 py-20">
|
||||
<div className="text-center mb-14">
|
||||
<h2 className="text-3xl font-heading font-bold text-white mb-3">{t('home.whyChooseUs')}</h2>
|
||||
<h2 className="text-3xl font-bold text-white mb-3">{t('home.whyChooseUs')}</h2>
|
||||
<p className="text-neutral-400">{t('home.whyChooseUsDesc')}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{[
|
||||
{ icon: '⚡', title: t('home.lightningFast'), desc: t('home.lightningFastDesc'), color: 'from-yellow-500/20 to-orange-500/20' },
|
||||
{ icon: '🔒', title: t('home.securePrivate'), desc: t('home.securePrivateDesc'), color: 'from-blue-500/20 to-cyan-500/20' },
|
||||
{ icon: '💰', title: t('home.payAsYouGo'), desc: t('home.payAsYouGoDesc'), color: 'from-green-500/20 to-emerald-500/20' },
|
||||
{ icon: (
|
||||
<svg className="w-6 h-6 text-violet-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
), title: t('home.lightningFast'), desc: t('home.lightningFastDesc'), gradient: 'from-violet-500/10 to-blue-500/10', border: 'border-violet-500/20' },
|
||||
{ icon: (
|
||||
<svg className="w-6 h-6 text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
), title: t('home.securePrivate'), desc: t('home.securePrivateDesc'), gradient: 'from-blue-500/10 to-cyan-500/10', border: 'border-blue-500/20' },
|
||||
{ icon: (
|
||||
<svg className="w-6 h-6 text-cyan-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
), title: t('home.payAsYouGo'), desc: t('home.payAsYouGoDesc'), gradient: 'from-cyan-500/10 to-teal-500/10', border: 'border-cyan-500/20' },
|
||||
].map((f, i) => (
|
||||
<TiltedCard
|
||||
key={i}
|
||||
rotateAmplitude={8}
|
||||
scaleOnHover={1.03}
|
||||
rotateAmplitude={6}
|
||||
scaleOnHover={1.02}
|
||||
borderRadius="16px"
|
||||
className="w-full"
|
||||
>
|
||||
<div className={`p-6 rounded-2xl bg-gradient-to-br ${f.color} border border-white/10 h-full`}>
|
||||
<div className="text-3xl mb-4">{f.icon}</div>
|
||||
<div className={`p-6 rounded-2xl bg-gradient-to-br ${f.gradient} border ${f.border} h-full`}>
|
||||
<div className="w-12 h-12 rounded-xl bg-white/[0.06] flex items-center justify-center mb-4">
|
||||
{f.icon}
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-white mb-2">{f.title}</h3>
|
||||
<p className="text-sm text-neutral-300 leading-relaxed">{f.desc}</p>
|
||||
<p className="text-sm text-neutral-400 leading-relaxed">{f.desc}</p>
|
||||
</div>
|
||||
</TiltedCard>
|
||||
))}
|
||||
@@ -122,13 +136,13 @@ export default function DefaultHome() {
|
||||
{enabledModels.length > 0 && (
|
||||
<section className="relative max-w-7xl mx-auto px-6 py-20">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-heading font-bold text-white mb-3">{t('home.availableModels')}</h2>
|
||||
<h2 className="text-3xl font-bold text-white mb-3">{t('home.availableModels')}</h2>
|
||||
<p className="text-neutral-400">{t('home.availableModelsDesc', { count: enabledModels.length })}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||
{enabledModels.slice(0, 12).map((m, i) => (
|
||||
<div key={m.id || i} className="px-4 py-3 rounded-xl bg-white/5 border border-white/10 flex items-center gap-3 hover:bg-white/10 transition-colors group">
|
||||
<div key={m.id || i} className="px-4 py-3 rounded-xl bg-white/[0.03] border border-white/[0.06] flex items-center gap-3 hover:bg-white/[0.06] transition-colors group">
|
||||
<div className="w-2 h-2 rounded-full bg-green-400 flex-shrink-0" />
|
||||
<span className="text-sm font-mono text-neutral-300 group-hover:text-white transition-colors truncate">
|
||||
{m.display_name || m.model_name}
|
||||
@@ -151,15 +165,16 @@ export default function DefaultHome() {
|
||||
{packages.length > 0 && (
|
||||
<section className="relative max-w-7xl mx-auto px-6 py-20">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-heading font-bold text-white mb-3">{t('home.plansPackages')}</h2>
|
||||
<h2 className="text-3xl font-bold text-white mb-3">{t('home.plansPackages')}</h2>
|
||||
<p className="text-neutral-400">{t('home.choosePlan')}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-6 max-w-4xl mx-auto">
|
||||
{packages.filter(p => p.enabled).slice(0, 3).map((pkg, i) => (
|
||||
<div key={pkg.id} className={`rounded-2xl p-6 flex flex-col border transition-all hover:scale-[1.02] ${
|
||||
i === 1 ? 'bg-white/10 border-white/20 shadow-xl' : 'bg-white/5 border-white/10'
|
||||
i === 1 ? 'bg-white/[0.06] border-white/[0.12] shadow-xl shadow-blue-500/5' : 'bg-white/[0.03] border-white/[0.06]'
|
||||
}`}>
|
||||
{i === 1 && <span className="text-xs text-blue-400 font-medium mb-2 uppercase tracking-wider">{t('home.popular') || 'Popular'}</span>}
|
||||
<h3 className="text-lg font-semibold text-white mb-1">{pkg.name}</h3>
|
||||
{pkg.description && <p className="text-sm text-neutral-400 mb-4">{pkg.description}</p>}
|
||||
<div className="mt-auto pt-4">
|
||||
@@ -171,7 +186,11 @@ export default function DefaultHome() {
|
||||
</div>
|
||||
{pkg.duration > 0 && <p className="text-xs text-neutral-500 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
||||
</div>
|
||||
<Link to={user ? '/packages' : '/register'} className="mt-4 py-2.5 rounded-xl bg-white text-black font-medium text-sm text-center hover:bg-neutral-100 transition-colors">
|
||||
<Link to={user ? '/packages' : '/register'} className={`mt-4 py-2.5 rounded-xl font-medium text-sm text-center transition-colors ${
|
||||
i === 1
|
||||
? 'bg-white text-black hover:bg-neutral-100'
|
||||
: 'bg-white/[0.06] text-white border border-white/[0.08] hover:bg-white/[0.1]'
|
||||
}`}>
|
||||
{user ? t('home.subscribe') : t('home.getStarted')}
|
||||
</Link>
|
||||
</div>
|
||||
@@ -182,10 +201,10 @@ export default function DefaultHome() {
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="relative max-w-7xl mx-auto px-6 py-24">
|
||||
<div className="rounded-3xl p-12 text-center relative overflow-hidden bg-gradient-to-br from-blue-600/20 via-purple-600/20 to-pink-600/20 border border-white/10">
|
||||
<div className="rounded-3xl p-12 text-center relative overflow-hidden bg-gradient-to-br from-violet-600/10 via-blue-600/10 to-cyan-600/10 border border-white/[0.08]">
|
||||
<div className="relative z-10">
|
||||
<h2 className="text-3xl font-heading font-bold text-white mb-4">{t('home.readyToStart')}</h2>
|
||||
<p className="text-neutral-300 mb-8 max-w-md mx-auto">{t('home.readyToStartDesc')}</p>
|
||||
<h2 className="text-3xl font-bold text-white mb-4">{t('home.readyToStart')}</h2>
|
||||
<p className="text-neutral-400 mb-8 max-w-md mx-auto">{t('home.readyToStartDesc')}</p>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
{user ? (
|
||||
<Link to="/dashboard" className="px-8 py-3 rounded-2xl bg-white text-black font-semibold hover:bg-neutral-100 transition-all">
|
||||
@@ -196,7 +215,7 @@ export default function DefaultHome() {
|
||||
<Link to="/register" className="px-8 py-3 rounded-2xl bg-white text-black font-semibold hover:bg-neutral-100 transition-all">
|
||||
{t('home.createFreeAccount')}
|
||||
</Link>
|
||||
<Link to="/login" className="px-8 py-3 rounded-2xl border border-white/20 text-white font-semibold hover:bg-white/10 transition-all">
|
||||
<Link to="/login" className="px-8 py-3 rounded-2xl border border-white/20 text-white font-medium hover:bg-white/10 transition-all">
|
||||
{t('home.signIn')}
|
||||
</Link>
|
||||
</>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Outlet, Link, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useSite } from '../../context/SiteContext';
|
||||
import LanguageSwitch from '../../components/LanguageSwitch';
|
||||
|
||||
export default function DefaultLayout() {
|
||||
const { t } = useTranslation();
|
||||
@@ -25,23 +26,23 @@ export default function DefaultLayout() {
|
||||
const visibleNavItems = navItems.filter((n) => !n.auth || user);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-[#0a0a1a]">
|
||||
<div className="min-h-screen flex flex-col bg-[#09090b]">
|
||||
{/* Announcement Banner */}
|
||||
{site?.announcement && (
|
||||
<div className="bg-gradient-to-r from-blue-600/20 via-purple-600/20 to-pink-600/20 border-b border-white/10 text-center py-2.5 px-4">
|
||||
<p className="text-sm text-neutral-200">{site.announcement}</p>
|
||||
<div className="bg-gradient-to-r from-violet-600/10 via-blue-600/10 to-cyan-600/10 border-b border-white/[0.06] text-center py-2.5 px-4">
|
||||
<p className="text-sm text-neutral-300">{site.announcement}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-50 backdrop-blur-2xl bg-[#0a0a1a]/80 border-b border-white/[0.08]">
|
||||
<header className="sticky top-0 z-50 backdrop-blur-2xl bg-[#09090b]/80 border-b border-white/[0.06]">
|
||||
<div className="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
|
||||
{/* Logo */}
|
||||
<Link to="/" className="flex items-center gap-3 group">
|
||||
{site?.logo ? (
|
||||
<img src={site.logo} alt={siteName} className="h-8 w-auto" />
|
||||
) : (
|
||||
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 flex items-center justify-center text-white font-bold text-sm shadow-lg shadow-purple-500/20">
|
||||
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-violet-500 via-blue-500 to-cyan-500 flex items-center justify-center text-white font-bold text-sm shadow-lg shadow-blue-500/20">
|
||||
{siteName.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
@@ -51,14 +52,14 @@ export default function DefaultLayout() {
|
||||
</Link>
|
||||
|
||||
{/* Desktop Nav */}
|
||||
<nav className="hidden md:flex items-center gap-1 bg-white/5 rounded-full px-2 py-1">
|
||||
<nav className="hidden md:flex items-center gap-1 bg-white/[0.04] rounded-full px-1.5 py-1 border border-white/[0.06]">
|
||||
{visibleNavItems.map((n) => (
|
||||
<Link
|
||||
key={n.to}
|
||||
to={n.to}
|
||||
className={`px-4 py-1.5 text-sm rounded-full transition-all ${
|
||||
location.pathname === n.to
|
||||
? 'text-white bg-white/10'
|
||||
? 'text-white bg-white/[0.08]'
|
||||
: 'text-neutral-400 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
@@ -67,8 +68,10 @@ export default function DefaultLayout() {
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Auth + Mobile toggle */}
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Auth + Lang + Mobile toggle */}
|
||||
<div className="flex items-center gap-2">
|
||||
<LanguageSwitch className="text-neutral-400 hover:text-white hover:bg-white/5" />
|
||||
|
||||
{user ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-neutral-400 hidden sm:block">
|
||||
@@ -83,7 +86,7 @@ export default function DefaultLayout() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Link to="/login" className="text-sm text-neutral-300 hover:text-white transition-colors px-3 py-1.5 hidden sm:block">
|
||||
<Link to="/login" className="text-sm text-neutral-400 hover:text-white transition-colors px-3 py-1.5 hidden sm:block">
|
||||
{t('nav.login')}
|
||||
</Link>
|
||||
<Link to="/register" className="px-5 py-2 rounded-full bg-white text-black text-sm font-medium hover:bg-neutral-100 transition-all">
|
||||
@@ -109,7 +112,7 @@ export default function DefaultLayout() {
|
||||
</div>
|
||||
|
||||
{mobileMenuOpen && (
|
||||
<div className="md:hidden border-t border-white/[0.08] bg-[#0a0a1a]/95 backdrop-blur-2xl">
|
||||
<div className="md:hidden border-t border-white/[0.06] bg-[#09090b]/95 backdrop-blur-2xl">
|
||||
<nav className="max-w-7xl mx-auto px-6 py-3 flex flex-col gap-1">
|
||||
{visibleNavItems.map((n) => (
|
||||
<Link
|
||||
@@ -134,7 +137,7 @@ export default function DefaultLayout() {
|
||||
<Outlet />
|
||||
</main>
|
||||
|
||||
<footer className="border-t border-white/[0.08] mt-auto bg-[#0a0a1a]">
|
||||
<footer className="border-t border-white/[0.06] mt-auto bg-[#09090b]">
|
||||
<div className="max-w-7xl mx-auto px-6 py-8 flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
<p className="text-sm text-neutral-500">© {new Date().getFullYear()} {siteName}.</p>
|
||||
<div className="flex items-center gap-4">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Outlet, Link, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useSite } from '../../context/SiteContext';
|
||||
import LanguageSwitch from '../../components/LanguageSwitch';
|
||||
|
||||
export default function MinimalLayout() {
|
||||
const { t } = useTranslation();
|
||||
@@ -63,7 +64,9 @@ export default function MinimalLayout() {
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<LanguageSwitch className="text-neutral-500 hover:text-white hover:bg-white/5" />
|
||||
|
||||
{user ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-neutral-500 hidden sm:block">{user.display_name || user.username}</span>
|
||||
|
||||
@@ -9,7 +9,6 @@ import BlurText from '../../components/bits/BlurText';
|
||||
import SpotlightCard from '../../components/bits/SpotlightCard';
|
||||
import CountUp from '../../components/bits/CountUp';
|
||||
import StarBorder from '../../components/bits/StarBorder';
|
||||
import ShinyText from '../../components/bits/ShinyText';
|
||||
|
||||
export default function StarterHome() {
|
||||
const { t } = useTranslation();
|
||||
@@ -40,7 +39,7 @@ export default function StarterHome() {
|
||||
<GradientText
|
||||
colors={['#818cf8', '#c084fc', '#f472b6', '#818cf8']}
|
||||
animationSpeed={6}
|
||||
className="text-5xl md:text-7xl font-heading font-extrabold leading-tight"
|
||||
className="text-5xl md:text-7xl font-extrabold leading-tight tracking-tight"
|
||||
>
|
||||
{site?.name || t('home.defaultHeroTitle')}
|
||||
</GradientText>
|
||||
@@ -48,7 +47,7 @@ export default function StarterHome() {
|
||||
|
||||
<BlurText
|
||||
text={t('home.heroSubtitle')}
|
||||
className="text-lg md:text-xl text-neutral-400 max-w-2xl mx-auto mb-10"
|
||||
className="text-lg md:text-xl text-neutral-400 max-w-2xl mx-auto mb-10 leading-relaxed"
|
||||
delay={50}
|
||||
animateBy="words"
|
||||
/>
|
||||
@@ -99,7 +98,7 @@ export default function StarterHome() {
|
||||
{/* Features Section */}
|
||||
<section className="relative max-w-7xl mx-auto px-6 py-20">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-heading font-bold text-white mb-3">{t('home.whyChooseUs')}</h2>
|
||||
<h2 className="text-3xl font-bold text-white mb-3">{t('home.whyChooseUs')}</h2>
|
||||
<p className="text-neutral-400">{t('home.whyChooseUsDesc')}</p>
|
||||
</div>
|
||||
|
||||
@@ -140,7 +139,7 @@ export default function StarterHome() {
|
||||
{enabledModels.length > 0 && (
|
||||
<section className="relative max-w-7xl mx-auto px-6 py-20">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-heading font-bold text-white mb-3">{t('home.availableModels')}</h2>
|
||||
<h2 className="text-3xl font-bold text-white mb-3">{t('home.availableModels')}</h2>
|
||||
<p className="text-neutral-400">
|
||||
{t('home.availableModelsDesc', { count: enabledModels.length })}
|
||||
</p>
|
||||
@@ -174,7 +173,7 @@ export default function StarterHome() {
|
||||
{packages.length > 0 && (
|
||||
<section className="relative max-w-7xl mx-auto px-6 py-20">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-heading font-bold text-white mb-3">{t('home.plansPackages')}</h2>
|
||||
<h2 className="text-3xl font-bold text-white mb-3">{t('home.plansPackages')}</h2>
|
||||
<p className="text-neutral-400">{t('home.choosePlan')}</p>
|
||||
</div>
|
||||
|
||||
@@ -213,7 +212,7 @@ export default function StarterHome() {
|
||||
<div className="glass rounded-3xl p-12 text-center relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-brand-600/10 via-transparent to-purple-600/10" />
|
||||
<div className="relative z-10">
|
||||
<h2 className="text-3xl font-heading font-bold text-white mb-4">
|
||||
<h2 className="text-3xl font-bold text-white mb-4">
|
||||
{t('home.readyToStart')}
|
||||
</h2>
|
||||
<p className="text-neutral-400 mb-8 max-w-md mx-auto">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Outlet, Link, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useSite } from '../../context/SiteContext';
|
||||
import LanguageSwitch from '../../components/LanguageSwitch';
|
||||
|
||||
export default function StarterLayout() {
|
||||
const { t } = useTranslation();
|
||||
@@ -59,8 +60,10 @@ export default function StarterLayout() {
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Auth + Mobile toggle */}
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Auth + Lang + Mobile toggle */}
|
||||
<div className="flex items-center gap-2">
|
||||
<LanguageSwitch className="text-neutral-400 hover:text-white hover:bg-white/5" />
|
||||
|
||||
{user ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-neutral-400 hidden sm:block">
|
||||
|
||||
Reference in new issue
Block a user