Publication IA: - 4 templates (magazine, brief, essay, simple) avec CSS riche - Rewrite IA (article/exercises/tutorial/reference/mixed) - Modération avec timeout 12s + fallback safe - Quotas publish_enhance par tier (basic=2, pro=15, business=100) - Détection contenu stale (hash) - Migration DB publishedContent/publishedTemplate/publishedSourceHash Fixes: - cheerio v1.2: Element -> AnyNode (domhandler), decodeEntities cast - _isShared ajouté au type Note (champ virtuel serveur) - callout colors PDF export: extraction fonction pure testable - admin/published: guard note.userId null - Cmd+S fonctionne en mode dialog (pas seulement fullPage) i18n: - 23 clés publish* traduites dans les 15 locales - Extension Web Clipper: 13 locales mise à jour Tests: - callout-colors.test.ts (6 tests) - note-visible-in-view.test.ts (5 tests) - entitlements.test.ts + byok-entitlements.test.ts: mock usageLog + unstubAllEnvs - 199/199 tests passent Tracker: user-stories.md sync avec sprint-status.yaml
413 lines
26 KiB
TypeScript
413 lines
26 KiB
TypeScript
'use client'
|
|
|
|
import { motion } from 'motion/react'
|
|
import {
|
|
BrainCircuit, Search, MessageSquare, Zap, Cpu, Workflow,
|
|
Globe, Shield, ArrowRight, Sparkles, Layers, Activity,
|
|
Box
|
|
} from 'lucide-react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { useLanguage } from '@/lib/i18n'
|
|
import { useState } from 'react'
|
|
|
|
export function LandingPage() {
|
|
const { t } = useLanguage()
|
|
const router = useRouter()
|
|
const [billingInterval, setBillingInterval] = useState<'monthly' | 'annual'>('monthly')
|
|
|
|
const AGENTS = [
|
|
{ key: 'scraper', icon: <Globe size={24} /> },
|
|
{ key: 'researcher', icon: <Search size={24} /> },
|
|
{ key: 'slideGen', icon: <Layers size={24} /> },
|
|
{ key: 'monitor', icon: <Activity size={24} /> },
|
|
{ key: 'diagramGen', icon: <Box size={24} /> },
|
|
{ key: 'custom', icon: <Workflow size={24} /> },
|
|
]
|
|
|
|
const BRAINSTORM_ITEMS = ['waveGeneration', 'collaboration', 'export']
|
|
|
|
const PLANS = [
|
|
{ key: 'basic', popular: false, price: t('landing.pricing.basicPrice'), period: '' },
|
|
{ key: 'pro', popular: true, price: billingInterval === 'monthly' ? '9,90€' : '7,90€', period: billingInterval === 'monthly' ? t('landing.pricing.perMonth') : t('landing.pricing.perMonthAnnual') },
|
|
{ key: 'business', popular: false, price: billingInterval === 'monthly' ? '29,90€' : '23,90€', period: billingInterval === 'monthly' ? t('landing.pricing.perMonth') : t('landing.pricing.perMonthAnnual') },
|
|
{ key: 'enterprise', popular: false, price: billingInterval === 'monthly' ? '49,90€' : '39,90€', period: billingInterval === 'monthly' ? t('landing.pricing.perUser') : t('landing.pricing.perUserAnnual') },
|
|
]
|
|
|
|
const TECH_TIERS = [
|
|
{ key: 'tags', color: 'bg-brand-accent' },
|
|
{ key: 'embeddings', color: 'bg-ochre' },
|
|
{ key: 'chatRag', color: 'bg-ink' },
|
|
]
|
|
|
|
const FOOTER_SECTIONS = ['product', 'community', 'legal'] as const
|
|
|
|
return (
|
|
<div className="min-h-screen bg-paper text-ink font-sans selection:bg-ochre/30 selection:text-ink">
|
|
{/* Navigation */}
|
|
<nav className="fixed top-0 left-0 right-0 z-[100] bg-paper/80 backdrop-blur-md border-b border-border px-8 py-4 flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-10 h-10 bg-ink flex items-center justify-center rounded-xl shadow-lg rotate-3 group hover:rotate-0 transition-transform cursor-pointer">
|
|
<span className="text-paper font-serif text-2xl font-bold">M</span>
|
|
</div>
|
|
<span className="font-serif text-2xl font-medium tracking-tight">Memento</span>
|
|
</div>
|
|
|
|
<div className="hidden md:flex items-center gap-10">
|
|
<a href="#features" className="text-[11px] font-bold uppercase tracking-widest text-concrete hover:text-ink transition-colors">{t('landing.nav.features')}</a>
|
|
<a href="#agents" className="text-[11px] font-bold uppercase tracking-widest text-concrete hover:text-ink transition-colors">{t('landing.nav.agents')}</a>
|
|
<a href="#brainstorm" className="text-[11px] font-bold uppercase tracking-widest text-concrete hover:text-ink transition-colors">{t('landing.nav.brainstorm')}</a>
|
|
<a href="#pricing" className="text-[11px] font-bold uppercase tracking-widest text-concrete hover:text-ink transition-colors">{t('landing.nav.pricing')}</a>
|
|
<a href="#tech" className="text-[11px] font-bold uppercase tracking-widest text-concrete hover:text-ink transition-colors">{t('landing.nav.tech')}</a>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4">
|
|
<button onClick={() => router.push('/login')} className="hidden md:block px-6 py-2.5 text-concrete hover:text-ink text-[11px] font-bold uppercase tracking-widest transition-colors">
|
|
{t('landing.nav.login')}
|
|
</button>
|
|
<button onClick={() => router.push('/register')} className="px-6 py-2.5 bg-ink text-paper rounded-full text-[11px] font-bold uppercase tracking-widest hover:opacity-90 transition-all flex items-center gap-2 group shadow-xl shadow-ink/10">
|
|
{t('landing.nav.cta')}
|
|
<ArrowRight size={14} className="group-hover:translate-x-1 transition-transform" />
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Hero */}
|
|
<section className="relative pt-40 pb-32 px-8 overflow-hidden">
|
|
<div className="absolute top-0 right-0 w-[800px] h-[800px] bg-ochre/5 rounded-full blur-[120px] -translate-y-1/2 translate-x-1/4 -z-10" />
|
|
<div className="absolute bottom-0 left-0 w-[600px] h-[600px] bg-brand-accent/5 rounded-full blur-[100px] translate-y-1/2 -translate-x-1/4 -z-10" />
|
|
|
|
<div className="max-w-6xl mx-auto text-center">
|
|
<motion.div initial={{ opacity: 0, y: 30 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.8, ease: [0.23, 1, 0.32, 1] }}>
|
|
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-ochre/10 border border-ochre/20 text-ochre text-[10px] font-bold uppercase tracking-[0.2em] mb-8">
|
|
<Sparkles size={12} />
|
|
{t('landing.hero.badge')}
|
|
</div>
|
|
<h1 className="text-6xl md:text-8xl font-serif font-medium tracking-tight text-ink mb-8 leading-[1.1]">
|
|
{t('landing.hero.title1')} <br />
|
|
<span className="italic">{t('landing.hero.title2')}</span>
|
|
</h1>
|
|
<p className="max-w-2xl mx-auto text-lg md:text-xl text-concrete font-light leading-relaxed mb-12">
|
|
{t('landing.hero.subtitle')}
|
|
</p>
|
|
|
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
|
<button onClick={() => router.push('/register')} className="px-10 py-5 bg-ink text-paper rounded-2xl text-sm font-bold uppercase tracking-[0.2em] hover:opacity-95 transition-all shadow-2xl shadow-ink/20 flex items-center gap-4 group">
|
|
{t('landing.hero.cta')}
|
|
<ArrowRight size={18} className="group-hover:translate-x-1 transition-transform" />
|
|
</button>
|
|
<a href="#features" className="px-10 py-5 border border-border rounded-2xl text-sm font-bold uppercase tracking-[0.2em] hover:bg-slate-50 transition-all">
|
|
{t('landing.hero.secondary')}
|
|
</a>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* App Preview Mockup */}
|
|
<motion.div initial={{ opacity: 0, y: 100 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 1, delay: 0.2, ease: [0.23, 1, 0.32, 1] }} className="mt-24 relative">
|
|
<div className="relative mx-auto max-w-5xl aspect-[16/10] bg-white rounded-[32px] shadow-[0_40px_100px_-20px_rgba(0,0,0,0.15)] border border-border p-4 overflow-hidden group">
|
|
<img src="/images/workspace-hero.jpg" alt="Memento Workspace" className="w-full h-full object-cover rounded-2xl filter saturate-[0.8]" />
|
|
<div className="absolute inset-0 bg-ink/10 group-hover:bg-ink/0 transition-colors duration-500" />
|
|
|
|
<div className="absolute top-10 right-10 w-64 bg-paper/90 backdrop-blur-xl border border-border p-6 rounded-2xl shadow-2xl">
|
|
<div className="flex items-center gap-3 mb-4">
|
|
<div className="w-8 h-8 rounded-full bg-brand-accent/20 flex items-center justify-center text-brand-accent">
|
|
<BrainCircuit size={16} />
|
|
</div>
|
|
<span className="text-[10px] font-bold uppercase tracking-widest">{t('landing.hero.memoryEcho')}</span>
|
|
</div>
|
|
<p className="text-xs font-serif italic text-ink/70">{t('landing.hero.memoryEchoText')}</p>
|
|
</div>
|
|
|
|
<div className="absolute bottom-10 left-10 w-72 bg-ink text-paper p-6 rounded-2xl shadow-2xl">
|
|
<div className="flex items-center gap-3 mb-4">
|
|
<Activity size={16} className="text-ochre" />
|
|
<span className="text-[10px] font-bold uppercase tracking-widest text-ochre">{t('landing.hero.brainstormLive')}</span>
|
|
</div>
|
|
<div className="flex items-center -space-x-2">
|
|
{[1, 2, 3].map(i => (
|
|
<div key={i} className="w-6 h-6 rounded-full border-2 border-ink bg-concrete text-[8px] flex items-center justify-center font-bold">JD</div>
|
|
))}
|
|
<span className="text-[10px] ml-4 text-paper/60">{t('landing.hero.ideasGenerated')}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Features */}
|
|
<section id="features" className="py-32 px-8 bg-paper">
|
|
<div className="max-w-6xl mx-auto">
|
|
<div className="mb-24 flex flex-col md:flex-row md:items-end justify-between gap-8">
|
|
<div className="max-w-2xl">
|
|
<span className="text-[11px] font-bold uppercase tracking-[0.3em] text-ochre mb-4 block">{t('landing.features.label')}</span>
|
|
<h2 className="text-4xl md:text-5xl font-serif tracking-tight text-ink">{t('landing.features.title')} <br />{t('landing.features.title2')}</h2>
|
|
</div>
|
|
<div className="text-concrete font-light">{t('landing.features.desc')}</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-12">
|
|
{['f1', 'f2', 'f3'].map((f, i) => {
|
|
const icons = [
|
|
<Search key="s" className="text-brand-accent" />,
|
|
<MessageSquare key="m" className="text-ochre" />,
|
|
<Zap key="z" className="text-ink" />
|
|
]
|
|
return (
|
|
<div key={f} className="group">
|
|
<div className="w-14 h-14 bg-slate-50 border border-border rounded-2xl flex items-center justify-center mb-8 group-hover:bg-ink group-hover:text-paper transition-all duration-300">
|
|
{icons[i]}
|
|
</div>
|
|
<h3 className="text-xl font-serif font-medium mb-4">{t(`landing.features.${f}Title`)}</h3>
|
|
<p className="text-sm text-concrete leading-relaxed font-light">{t(`landing.features.${f}Desc`)}</p>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Agents */}
|
|
<section id="agents" className="py-32 px-8 bg-ink text-paper overflow-hidden relative">
|
|
<div className="absolute top-0 right-0 w-[1000px] h-[1000px] bg-brand-accent/10 rounded-full blur-[150px] -translate-y-1/2 translate-x-1/2" />
|
|
<div className="max-w-6xl mx-auto relative z-10">
|
|
<div className="text-center mb-24">
|
|
<span className="text-[11px] font-bold uppercase tracking-[0.3em] text-ochre mb-4 block">{t('landing.agents.label')}</span>
|
|
<h2 className="text-4xl md:text-6xl font-serif tracking-tight mb-8">{t('landing.agents.title')}</h2>
|
|
<p className="text-paper/60 max-w-xl mx-auto font-light">{t('landing.agents.desc')}</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
|
{AGENTS.map((agent, i) => (
|
|
<div key={i} className="p-8 rounded-3xl bg-white/5 border border-white/10 hover:bg-white/10 transition-all cursor-default group">
|
|
<div className="text-ochre mb-6 transition-transform group-hover:scale-110 duration-300">{agent.icon}</div>
|
|
<h4 className="text-xl font-serif font-medium mb-4">{t(`landing.agents.${agent.key}.title`)}</h4>
|
|
<p className="text-sm text-paper/50 leading-relaxed font-light">{t(`landing.agents.${agent.key}.desc`)}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Brainstorm */}
|
|
<section id="brainstorm" className="py-32 px-8 bg-paper">
|
|
<div className="max-w-6xl mx-auto flex flex-col md:flex-row items-center gap-24">
|
|
<div className="flex-1">
|
|
<span className="text-[11px] font-bold uppercase tracking-[0.3em] text-ochre mb-4 block">{t('landing.brainstorm.label')}</span>
|
|
<h2 className="text-4xl md:text-5xl font-serif tracking-tight text-ink mb-8 leading-tight">{t('landing.brainstorm.title')}</h2>
|
|
<div className="space-y-8">
|
|
{BRAINSTORM_ITEMS.map((item, i) => (
|
|
<div key={item} className="flex gap-6">
|
|
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-ochre/10 text-ochre flex items-center justify-center font-bold text-xs">{i + 1}</div>
|
|
<div>
|
|
<h5 className="font-bold text-sm mb-1">{t(`landing.brainstorm.${item}.title`)}</h5>
|
|
<p className="text-sm text-concrete font-light leading-relaxed">{t(`landing.brainstorm.${item}.desc`)}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 relative">
|
|
<div className="w-[450px] h-[450px] border-2 border-dashed border-border rounded-full flex items-center justify-center relative">
|
|
<div className="absolute top-0 right-1/2 translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-ink rounded-full" />
|
|
<div className="absolute bottom-0 right-1/2 translate-x-1/2 translate-y-1/2 w-4 h-4 bg-ochre rounded-full" />
|
|
<div className="w-[300px] h-[300px] border-2 border-dashed border-border rounded-full flex items-center justify-center">
|
|
<div className="w-[150px] h-[150px] border-2 border-dashed border-border rounded-full flex items-center justify-center">
|
|
<div className="w-12 h-12 bg-ink rounded-xl shadow-2xl flex items-center justify-center text-paper font-serif text-xl">M</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="absolute top-10 right-0 p-4 bg-white border border-border rounded-xl shadow-xl">
|
|
<p className="text-[10px] font-bold text-ochre">{t('landing.brainstorm.disruptionLabel')}</p>
|
|
<p className="text-xs font-serif italic text-ink">{t('landing.brainstorm.disruptionText')}</p>
|
|
</div>
|
|
<div className="absolute bottom-20 -left-10 p-4 bg-white border border-border rounded-xl shadow-xl">
|
|
<p className="text-[10px] font-bold text-brand-accent">{t('landing.brainstorm.analogyLabel')}</p>
|
|
<p className="text-xs font-serif italic text-ink">{t('landing.brainstorm.analogyText')}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Tech */}
|
|
<section id="tech" className="py-32 px-8 bg-slate-50 border-y border-border">
|
|
<div className="max-w-6xl mx-auto text-center">
|
|
<span className="text-[11px] font-bold uppercase tracking-[0.3em] text-ochre mb-4 block">{t('landing.tech.label')}</span>
|
|
<h2 className="text-4xl font-serif tracking-tight mb-16">{t('landing.tech.title')}</h2>
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-8 grayscale opacity-50 hover:grayscale-0 hover:opacity-100 transition-all duration-700">
|
|
{['OpenAI', 'Google', 'Anthropic', 'DeepSeek', 'Mistral', 'Meta', 'Ollama', 'Groq', 'X.AI', 'Custom'].map((brand, i) => (
|
|
<div key={i} className="flex flex-col items-center gap-3">
|
|
<div className="w-12 h-12 bg-white rounded-xl border border-border flex items-center justify-center text-xs font-black tracking-tighter">
|
|
{brand.slice(0, 2).toUpperCase()}
|
|
</div>
|
|
<span className="text-[10px] font-bold uppercase tracking-widest">{brand}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="mt-24 max-w-2xl mx-auto p-1 bg-white rounded-3xl border border-border shadow-sm flex flex-col md:flex-row gap-0.5">
|
|
{TECH_TIERS.map((tier, i) => (
|
|
<div key={i} className="flex-1 p-6 text-left">
|
|
<div className={`w-1.5 h-1.5 rounded-full ${tier.color} mb-4`} />
|
|
<h6 className="text-[10px] font-bold uppercase tracking-widest text-concrete mb-2">{t(`landing.tech.${tier.key}.title`)}</h6>
|
|
<p className="text-xs font-light text-concrete">{t(`landing.tech.${tier.key}.desc`)}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Pricing */}
|
|
<section id="pricing" className="py-32 px-8 bg-paper">
|
|
<div className="max-w-7xl mx-auto">
|
|
<div className="text-center mb-12">
|
|
<span className="text-[11px] font-bold uppercase tracking-[0.3em] text-ochre mb-4 block">{t('landing.pricing.label')}</span>
|
|
<h2 className="text-4xl md:text-5xl font-serif tracking-tight text-ink mb-6">{t('landing.pricing.title')}</h2>
|
|
<p className="text-concrete font-light max-w-xl mx-auto mb-12">{t('landing.pricing.desc')}</p>
|
|
|
|
<div className="flex items-center justify-center gap-10 mb-8">
|
|
<button onClick={() => setBillingInterval('monthly')} className={`group relative py-2 px-1 transition-all ${billingInterval === 'monthly' ? 'text-ink' : 'text-concrete/40 hover:text-concrete'}`}>
|
|
<span className="text-xs font-black uppercase tracking-[0.2em]">{t('landing.pricing.monthly')}</span>
|
|
{billingInterval === 'monthly' && (
|
|
<motion.div layoutId="interval-active" className="absolute -inset-x-1 -inset-y-0.5 border border-ochre/60" transition={{ type: 'spring', bounce: 0.2, duration: 0.6 }} />
|
|
)}
|
|
</button>
|
|
<div className="relative">
|
|
<button onClick={() => setBillingInterval('annual')} className={`group relative py-2 px-1 transition-all ${billingInterval === 'annual' ? 'text-ink' : 'text-concrete/40 hover:text-concrete'}`}>
|
|
<span className="text-xs font-black uppercase tracking-[0.2em]">{t('landing.pricing.annual')}</span>
|
|
{billingInterval === 'annual' && (
|
|
<motion.div layoutId="interval-active" className="absolute -inset-x-1 -inset-y-0.5 border border-ochre/60" transition={{ type: 'spring', bounce: 0.2, duration: 0.6 }} />
|
|
)}
|
|
</button>
|
|
<div className="absolute -top-6 left-1/2 -translate-x-1/2 whitespace-nowrap">
|
|
<span className="text-[9px] font-bold text-ochre uppercase tracking-widest italic animate-pulse">(-20%)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 items-stretch">
|
|
{PLANS.map((plan, i) => (
|
|
<div key={plan.key} className={`relative p-8 rounded-[32px] border flex flex-col transition-all duration-300 hover:shadow-2xl hover:shadow-ink/5 ${plan.popular ? 'bg-ink text-paper border-ink ring-4 ring-ochre/20' : 'bg-white border-border text-ink'}`}>
|
|
{plan.popular && (
|
|
<div className="absolute -top-4 left-1/2 -translate-x-1/2 px-4 py-1 bg-ochre text-ink text-[10px] font-bold uppercase tracking-widest rounded-full">
|
|
{t('landing.pricing.popular')}
|
|
</div>
|
|
)}
|
|
<div className="mb-8">
|
|
<h4 className="text-[11px] font-bold uppercase tracking-widest mb-2 opacity-60">{t(`landing.pricing.${plan.key}.name`)}</h4>
|
|
<div className="flex items-baseline gap-1 mb-4">
|
|
<span className="text-4xl font-serif font-medium">{plan.price}</span>
|
|
{plan.period && <span className="text-xs opacity-60">{plan.period}</span>}
|
|
</div>
|
|
<p className="text-sm font-light leading-relaxed opacity-80">{t(`landing.pricing.${plan.key}.desc`)}</p>
|
|
</div>
|
|
<div className="flex-1 space-y-4 mb-10">
|
|
{[0, 1, 2, 3, 4, 5].map(j => {
|
|
const feat = t(`landing.pricing.${plan.key}.feature${j}`)
|
|
if (!feat || feat === `landing.pricing.${plan.key}.feature${j}`) return null
|
|
return (
|
|
<div key={j} className="flex items-start gap-3">
|
|
<div className={`mt-1 rounded-full p-0.5 ${plan.popular ? 'bg-ochre text-ink' : 'bg-brand-accent/10 text-brand-accent'}`}>
|
|
<Shield size={10} fill="currentColor" />
|
|
</div>
|
|
<span className="text-xs font-light">{feat}</span>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
<button onClick={() => router.push('/register')} className={`w-full py-4 rounded-2xl text-xs font-bold uppercase tracking-widest transition-all ${plan.popular ? 'bg-ochre text-ink hover:opacity-90' : 'bg-ink text-paper hover:bg-ink/90'}`}>
|
|
{t(`landing.pricing.${plan.key}.cta`)}
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* BYOK */}
|
|
<div className="mt-20 p-12 bg-slate-50 border border-border rounded-[40px] flex flex-col md:flex-row items-center gap-12">
|
|
<div className="flex-1">
|
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-brand-accent/10 text-brand-accent text-[9px] font-bold uppercase tracking-widest mb-6">
|
|
<Cpu size={12} />
|
|
{t('landing.byok.label')}
|
|
</div>
|
|
<h3 className="text-3xl font-serif font-medium mb-4">{t('landing.byok.title')}</h3>
|
|
<p className="text-concrete font-light leading-relaxed mb-6">{t('landing.byok.desc')}</p>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="p-4 bg-white rounded-2xl border border-border">
|
|
<h5 className="text-[10px] font-bold uppercase tracking-widest mb-2">{t('landing.byok.noLockin')}</h5>
|
|
<p className="text-[10px] text-concrete font-light">{t('landing.byok.noLockinDesc')}</p>
|
|
</div>
|
|
<div className="p-4 bg-white rounded-2xl border border-border">
|
|
<h5 className="text-[10px] font-bold uppercase tracking-widest mb-2">{t('landing.byok.cost')}</h5>
|
|
<p className="text-[10px] text-concrete font-light">{t('landing.byok.costDesc')}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="w-full md:w-[400px] bg-ink rounded-3xl p-8 relative overflow-hidden group">
|
|
<div className="absolute inset-0 bg-brand-accent/10 blur-[50px] group-hover:bg-ochre/10 transition-colors" />
|
|
<div className="relative z-10 font-mono text-[10px] text-paper/40 space-y-2">
|
|
<p className="text-ochre">{"{"}</p>
|
|
<p className="pl-4">"provider": "anthropic",</p>
|
|
<p className="pl-4">"model": "claude-3-opus",</p>
|
|
<p className="pl-4 border-l border-brand-accent/30 bg-brand-accent/5">"apiKey": "sk-ant-at03-..."</p>
|
|
<p className="pl-4">"useSystemKey": false</p>
|
|
<p className="text-ochre">{"}"}</p>
|
|
</div>
|
|
<div className="mt-8 flex items-center justify-between relative z-10">
|
|
<span className="text-[10px] font-bold text-paper uppercase tracking-widest">{t('landing.byok.configLabel')}</span>
|
|
<div className="flex gap-1">{[1, 2, 3].map(i => <div key={i} className="w-1 h-1 rounded-full bg-paper/20" />)}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Final CTA */}
|
|
<section className="py-40 px-8 text-center bg-paper relative overflow-hidden">
|
|
<div className="max-w-3xl mx-auto relative z-10">
|
|
<h2 className="text-5xl md:text-7xl font-serif tracking-tight mb-8 leading-tight">{t('landing.cta.title1')} <br /><span className="italic">{t('landing.cta.title2')}</span></h2>
|
|
<p className="text-lg text-concrete font-light mb-12">{t('landing.cta.desc')}</p>
|
|
<button onClick={() => router.push('/register')} className="px-16 py-6 bg-ink text-paper rounded-[32px] text-lg font-bold uppercase tracking-[0.2em] hover:scale-105 transition-all shadow-[0_30px_60px_-15px_rgba(0,0,0,0.3)]">
|
|
{t('landing.cta.button')}
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Footer */}
|
|
<footer className="py-20 px-8 border-t border-border bg-paper">
|
|
<div className="max-w-6xl mx-auto flex flex-col md:flex-row justify-between gap-12">
|
|
<div className="space-y-6">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 bg-ink flex items-center justify-center rounded-lg">
|
|
<span className="text-paper font-serif text-lg font-bold">M</span>
|
|
</div>
|
|
<span className="font-serif text-xl font-medium tracking-tight">Memento</span>
|
|
</div>
|
|
<p className="text-sm text-concrete font-light max-w-xs">{t('landing.footer.desc')}</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-16">
|
|
{FOOTER_SECTIONS.map(section => (
|
|
<div key={section} className="space-y-4">
|
|
<h6 className="text-[10px] font-bold uppercase tracking-widest text-ink">{t(`landing.footer.${section}.title`)}</h6>
|
|
<ul className="space-y-2 text-sm text-concrete font-light">
|
|
{[0, 1, 2].map(j => {
|
|
const label = t(`landing.footer.${section}.link${j}`)
|
|
const href = t(`landing.footer.${section}.link${j}Href`)
|
|
if (!label || label.startsWith('landing.')) return null
|
|
return <li key={j}><a href={href} className="hover:text-ink">{label}</a></li>
|
|
})}
|
|
</ul>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="max-w-6xl mx-auto mt-20 pt-10 border-t border-border flex flex-col md:flex-row justify-between items-center gap-4 text-[10px] uppercase font-bold tracking-widest text-concrete">
|
|
<div>© 2026 MOMENTO LABS. ALL RIGHTS RESERVED.</div>
|
|
<div className="flex gap-8"><span>DESIGNED BY ANTIGRAVITY</span></div>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
)
|
|
}
|