fix: integrate deepseek, resolve silent google api errors, fix google cloud keys
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2s

This commit is contained in:
2026-05-17 17:11:06 +02:00
parent 669cf7fde8
commit b50419e2ec
2062 changed files with 272359 additions and 523 deletions

View File

@@ -0,0 +1,664 @@
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import {
FileText,
Layers,
MousePointer2,
Download,
Globe,
Cpu,
CheckCircle2,
ArrowRight,
Layout,
PieChart,
FileSpreadsheet,
Presentation,
MessageSquare,
ShieldCheck,
Zap,
Menu,
X,
Languages,
Monitor,
Eye,
Lock,
Clock
} from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
const Navbar = () => {
const [isOpen, setIsOpen] = useState(false);
const { t, i18n } = useTranslation();
const toggleLanguage = () => {
const nextLang = i18n.language === 'fr' ? 'en' : 'fr';
i18n.changeLanguage(nextLang);
};
return (
<nav className="fixed top-4 left-1/2 -translate-x-1/2 w-[calc(100%-2rem)] max-w-7xl z-50">
<div className="glass rounded-3xl px-6 py-4 shadow-warm">
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-gradient-to-br from-primary via-secondary to-accent rounded-xl flex items-center justify-center shadow-lg shadow-primary/10">
<span className="text-white font-black text-xl">W</span>
</div>
<div className="flex flex-col -gap-1">
<span className="text-xl font-black tracking-tight text-primary">WORDLY<span className="text-secondary">.art</span></span>
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-gray-400 leading-none">Translation Suite</span>
</div>
</div>
<div className="hidden md:flex items-center space-x-8">
<a href="#features" className="text-gray-500 hover:text-primary transition-colors text-sm font-semibold tracking-tight">{t('nav.why')}</a>
<a href="#formats" className="text-gray-500 hover:text-primary transition-colors text-sm font-semibold tracking-tight">{t('nav.formats')}</a>
<a href="#pricing" className="text-gray-500 hover:text-primary transition-colors text-sm font-semibold tracking-tight">{t('nav.pricing')}</a>
<div className="h-4 w-[1px] bg-gray-200"></div>
<button
onClick={toggleLanguage}
className="text-[10px] font-black bg-gray-50 px-2 py-1 rounded border border-gray-100 text-primary hover:bg-gray-100 transition-colors uppercase"
>
{i18n.language === 'fr' ? 'EN' : 'FR'}
</button>
<a href="#" className="text-primary hover:opacity-80 transition-opacity text-sm font-bold tracking-tight">{t('nav.login')}</a>
<a href="#" className="bg-primary text-white px-6 py-2.5 rounded-2xl text-sm font-bold hover:shadow-lg hover:shadow-primary/20 transition-all active:scale-95">
{t('nav.startFree')}
</a>
</div>
<div className="md:hidden">
<button onClick={() => setIsOpen(!isOpen)} className="text-primary p-2 hover:bg-gray-100 rounded-lg transition-colors">
{isOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
</div>
</div>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="md:hidden bg-white border-b border-gray-100 overflow-hidden"
>
<div className="px-6 pt-4 pb-10 space-y-4 text-center">
<button
onClick={toggleLanguage}
className="w-full py-2 text-sm font-black text-[#004777] bg-gray-50 rounded-xl"
>
{t('common.language', { lang: i18n.language === 'fr' ? 'English' : 'Français' })}
<span className="ml-2 opacity-50 uppercase">{i18n.language === 'fr' ? 'en' : 'fr'}</span>
</button>
<a href="#features" onClick={() => setIsOpen(false)} className="block py-3 text-lg font-bold text-gray-700">{t('nav.why')}</a>
<a href="#formats" onClick={() => setIsOpen(false)} className="block py-3 text-lg font-bold text-gray-700">{t('nav.formats')}</a>
<a href="#pricing" onClick={() => setIsOpen(false)} className="block py-3 text-lg font-bold text-gray-700">{t('nav.pricing')}</a>
<div className="pt-6 grid grid-cols-2 gap-4">
<a href="#" className="flex items-center justify-center py-4 text-[#004777] font-black border-2 border-[#004777]/10 rounded-2xl">{t('nav.login')}</a>
<a href="#" className="flex items-center justify-center py-4 bg-[#004777] text-white rounded-2xl font-black">{t('nav.startFree')}</a>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</nav>
);
};
const Hero = () => {
const { t } = useTranslation();
return (
<section className="relative pt-40 pb-24 lg:pt-56 lg:pb-40 px-4 overflow-hidden bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-secondary/5 via-white to-white">
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-full h-full max-w-7xl pointer-events-none -z-10">
<div className="absolute top-[-10%] right-[-10%] w-[600px] h-[600px] bg-secondary/10 blur-[120px] rounded-full"></div>
<div className="absolute bottom-[20%] left-[-10%] w-[500px] h-[500px] bg-accent/10 blur-[120px] rounded-full"></div>
</div>
<div className="max-w-7xl mx-auto grid lg:grid-cols-12 gap-16 items-center">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: "easeOut" }}
className="lg:col-span-7"
>
<div className="inline-flex items-center gap-2 px-3 py-1 mb-8 text-[10px] font-black tracking-[0.25em] text-secondary uppercase bg-secondary/5 rounded-full border border-secondary/10">
<Languages size={12} /> {t('hero.tag')}
</div>
<h1 className="text-6xl lg:text-[5.5rem] font-black tracking-tight text-primary mb-8 leading-[0.9] lg:max-w-3xl">
{t('hero.titleLine1')}<br />
<span className="gradient-text">{t('hero.titleLine2')}</span>
</h1>
<p className="text-xl text-gray-500 mb-10 max-w-xl leading-relaxed font-medium">
{t('hero.description')}
</p>
<div className="flex flex-col sm:flex-row gap-5 mb-16">
<button className="flex items-center justify-center gap-3 bg-primary text-white px-10 py-5 rounded-2xl text-lg font-bold hover:scale-[1.02] active:scale-95 transition-all shadow-xl shadow-primary/20">
{t('hero.ctaMain')} <ArrowRight size={20} className="text-accent" />
</button>
<button className="flex items-center justify-center gap-2 text-primary font-bold text-lg hover:translate-x-1 transition-transform bg-white border border-gray-200 px-8 py-5 rounded-2xl shadow-sm hover:shadow-md">
{t('hero.ctaSec')}
</button>
</div>
<div className="flex flex-wrap gap-8 items-center pt-8 border-t border-gray-100">
{[
{ icon: <Clock size={16} className="text-accent" />, text: t('hero.deleted') },
{ icon: <ShieldCheck size={16} className="text-secondary" />, text: t('hero.noHidden') },
{ icon: <Eye size={16} className="text-primary" />, text: t('hero.preview') }
].map((item, i) => (
<div key={i} className="flex items-center gap-2.5 text-[10px] font-black uppercase tracking-widest text-gray-400">
{item.icon} {item.text}
</div>
))}
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 40 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
transition={{ duration: 1, ease: [0.16, 1, 0.3, 1], delay: 0.2 }}
className="lg:col-span-5 relative"
>
<div className="absolute -inset-4 bg-gradient-to-tr from-primary/10 via-secondary/10 to-accent/10 rounded-[40px] blur-2xl opacity-50"></div>
<div className="relative bg-white p-4 rounded-[36px] shadow-2xl border border-white/50">
<div className="aspect-[4/5] lg:aspect-[4/3] bg-[#F1F5F9] rounded-[28px] relative overflow-hidden group">
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center p-8">
<div className="relative mb-10 inline-block">
<div className="absolute inset-0 bg-secondary/10 blur-3xl rounded-full scale-150"></div>
<motion.div
animate={{ rotate: [12, -12, 12] }}
transition={{ duration: 10, repeat: Infinity, ease: "easeInOut" }}
className="relative z-10 w-28 h-28 bg-white rounded-[32px] shadow-2xl flex items-center justify-center"
>
<FileText size={56} className="text-primary" />
</motion.div>
<motion.div
animate={{ rotate: [-12, 12, -12] }}
transition={{ duration: 8, repeat: Infinity, ease: "easeInOut" }}
className="absolute -bottom-4 -right-4 w-14 h-14 bg-secondary rounded-2xl shadow-lg flex items-center justify-center text-white"
>
<Languages size={24} />
</motion.div>
</div>
<div className="space-y-4 max-w-[200px] mx-auto">
<div className="h-1.5 w-full bg-gray-200 rounded-full"></div>
<div className="h-1.5 w-[80%] bg-gray-200 rounded-full mx-auto opacity-60"></div>
<div className="h-1.5 w-[60%] bg-gray-200 rounded-full mx-auto opacity-40"></div>
<div className="pt-6">
<span className="text-2xl font-black text-primary tracking-tight">WORDLY<span className="text-secondary">.art</span></span>
</div>
</div>
</div>
</div>
<div className="absolute top-6 right-6 p-4 glass shadow-lg rounded-2xl text-[10px] font-bold">
<div className="flex items-center gap-2 text-secondary"><CheckCircle2 size={14} /> {t('hero.formattedOk')}</div>
</div>
<div className="absolute bottom-6 left-6 p-4 bg-primary text-white shadow-xl rounded-2xl text-[10px] font-bold ring-1 ring-white/20">
<div className="flex items-center gap-2"><Zap size={14} className="text-accent" /> {t('hero.aiActive')}</div>
</div>
</div>
</div>
</motion.div>
</div>
</section>
);
};
const Steps = () => {
const { t } = useTranslation();
const steps = [
{
num: t('steps.step1.num'),
title: t('steps.step1.title'),
desc: t('steps.step1.desc'),
icon: <MousePointer2 size={28} />,
gradient: "from-blue-500/10 to-primary/10",
iconColor: "text-primary"
},
{
num: t('steps.step2.num'),
title: t('steps.step2.title'),
desc: t('steps.step2.desc'),
icon: <Cpu size={28} />,
gradient: "from-secondary/10 to-secondary/20",
iconColor: "text-secondary"
},
{
num: t('steps.step3.num'),
title: t('steps.step3.title'),
desc: t('steps.step3.desc'),
icon: <Download size={28} />,
gradient: "from-accent/20 to-accent/40",
iconColor: "text-primary"
}
];
return (
<section className="py-24 px-4 bg-white relative overflow-hidden">
<div className="max-w-7xl mx-auto text-center relative z-10">
<h2 className="text-4xl lg:text-5xl font-black text-primary mb-6">{t('steps.title')}</h2>
<p className="text-gray-400 mb-20 text-lg max-w-2xl mx-auto">{t('steps.subtitle')}</p>
<div className="grid md:grid-cols-3 gap-8 relative">
{steps.map((st, i) => (
<div key={i} className="group p-10 rounded-[40px] bg-gray-50/50 hover:bg-white border border-transparent hover:border-gray-100 hover:shadow-warm transition-all duration-500 text-left">
<div className={`w-16 h-16 bg-gradient-to-br ${st.gradient} rounded-2xl flex items-center justify-center ${st.iconColor} mb-8 transition-transform duration-500 group-hover:scale-110 group-hover:rotate-3`}>
{st.icon}
</div>
<div className="text-[10px] font-black tracking-[0.25em] text-secondary mb-4 uppercase">{t('steps.stepLabel', { num: st.num })}</div>
<h3 className="text-2xl font-black text-primary mb-4">{st.title}</h3>
<p className="text-gray-500 leading-relaxed text-sm font-medium">{st.desc}</p>
</div>
))}
</div>
</div>
</section>
);
};
const FeaturesContext = () => {
const { t } = useTranslation();
return (
<section id="features" className="py-24 px-4 bg-gray-50/30">
<div className="max-w-7xl mx-auto">
<div className="grid lg:grid-cols-2 gap-20 items-center">
<div>
<div className="inline-flex items-center gap-2 text-[10px] font-black tracking-[0.2em] text-secondary uppercase mb-6">
<Zap size={14} className="text-accent" /> {t('features.tag')}
</div>
<h2 className="text-4xl lg:text-5xl font-black text-primary mb-8 leading-tight">
{t('features.title')}
</h2>
<p className="text-lg text-gray-500 mb-12 font-medium">
{t('features.description')}
</p>
<div className="space-y-8">
{[
{ icon: <Monitor className="text-primary" />, title: t('features.context.title'), desc: t('features.context.desc') },
{ icon: <Layers className="text-secondary" />, title: t('features.glossary.title'), desc: t('features.glossary.desc') },
{ icon: <Eye className="text-primary" />, title: t('features.vision.title'), desc: t('features.vision.desc') }
].map((item, i) => (
<div key={i} className="flex gap-6 group">
<div className="flex-shrink-0 w-14 h-14 bg-white rounded-2xl shadow-sm flex items-center justify-center border border-gray-100 group-hover:shadow-md transition-shadow">
{item.icon}
</div>
<div>
<h4 className="text-lg font-black text-primary mb-1">{item.title}</h4>
<p className="text-gray-500 text-sm leading-relaxed font-medium">{item.desc}</p>
</div>
</div>
))}
</div>
</div>
<div className="relative">
<div className="absolute -inset-10 bg-primary/5 blur-[100px] rounded-full"></div>
<div className="relative bg-white rounded-[48px] shadow-warm border border-gray-100 p-8 lg:p-12">
<div className="space-y-8">
<div>
<span className="text-[10px] font-black uppercase tracking-widest text-primary/40 block mb-4">{t('features.demo.source')}</span>
<div className="text-sm font-bold bg-gray-50 p-6 rounded-2xl italic text-primary/80">La batterie de chauffe est raccordée sur la CTA</div>
</div>
<div className="grid sm:grid-cols-2 gap-6">
<div className="p-6 border border-red-50 bg-red-50/10 rounded-2xl">
<span className="text-[10px] font-black uppercase tracking-widest text-red-400 block mb-3">{t('features.demo.google')}</span>
<div className="text-xs font-semibold text-red-900/40 line-through">The heating battery is connected on the <span className="text-red-700">call to action</span></div>
</div>
<div className="p-6 bg-secondary/5 rounded-2xl ring-1 ring-secondary/20 shadow-sm">
<span className="text-[10px] font-black uppercase tracking-widest text-secondary block mb-3">{t('features.demo.ours')}</span>
<div className="text-sm font-bold text-primary">The heating coil is connected to the <span className="bg-accent/30 px-1 rounded">AHU</span></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
const LayoutFeatures = () => {
const { t } = useTranslation();
const points = (t('layout.points', { returnObjects: true }) as any[]).map((p, i) => ({
...p,
icon: i === 0 ? <Layers size={22} /> :
i === 1 ? <Layout size={22} /> :
i === 2 ? <PieChart size={22} /> :
i === 3 ? <Zap size={22} /> :
i === 4 ? <MessageSquare size={22} /> :
<Globe size={22} />
}));
return (
<section className="py-24 px-4 bg-white relative overflow-hidden">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-24 transition-opacity">
<h2 className="text-4xl lg:text-6xl font-black text-primary mb-8 leading-tight">
{t('layout.title')}<br /> <span className="gradient-text">{t('layout.title2')}</span>
</h2>
<p className="text-gray-400 max-w-2xl mx-auto text-lg font-medium">
{t('layout.subtitle')}
</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{points.map((p, i) => (
<motion.div
key={i}
whileHover={{ y: -8 }}
className="group p-10 bg-gray-50/50 hover:bg-white rounded-[40px] border border-transparent hover:border-gray-100 hover:shadow-warm transition-all duration-500"
>
<div className="w-14 h-14 bg-white rounded-2xl flex items-center justify-center text-primary mb-8 shadow-sm group-hover:bg-primary group-hover:text-white transition-all transform group-hover:rotate-6">
{p.icon}
</div>
<h4 className="text-xl font-black text-primary mb-4">{p.title}</h4>
<p className="text-gray-500 text-sm leading-relaxed font-medium">{p.desc}</p>
</motion.div>
))}
</div>
</div>
</section>
);
};
const FormatsSection = () => {
const { t } = useTranslation();
const formats = [
{
id: ".DOCX",
name: t('formats.word.name'),
icon: <FileText className="text-blue-500" />,
items: t('formats.word.items', { returnObjects: true }) as string[],
},
{
id: ".XLSX",
name: t('formats.excel.name'),
icon: <FileSpreadsheet className="text-green-600" />,
items: t('formats.excel.items', { returnObjects: true }) as string[],
},
{
id: ".PPTX",
name: t('formats.pptx.name'),
icon: <Presentation className="text-orange-500" />,
items: t('formats.pptx.items', { returnObjects: true }) as string[],
},
{
id: ".PDF",
name: t('formats.pdf.name'),
icon: <FileText className="text-red-500" />,
items: t('formats.pdf.items', { returnObjects: true }) as string[],
}
];
return (
<section id="formats" className="py-24 px-4 bg-primary text-white overflow-hidden relative">
<div className="absolute top-0 left-0 w-full h-full pointer-events-none opacity-20">
<div className="absolute top-[-20%] right-[-10%] w-[800px] h-[800px] bg-secondary/30 blur-[150px] rounded-full"></div>
<div className="absolute bottom-[-20%] left-[-10%] w-[600px] h-[600px] bg-accent/20 blur-[120px] rounded-full"></div>
</div>
<div className="max-w-7xl mx-auto relative z-10">
<div className="text-center mb-24">
<h2 className="text-4xl lg:text-6xl font-black mb-8 leading-tight">{t('formats.title')} <span className="text-accent">{t('formats.title2')}</span></h2>
<p className="text-gray-300 max-w-xl mx-auto text-lg font-medium">{t('formats.subtitle')}</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
{formats.map((f, i) => (
<motion.div
key={i}
whileHover={{ y: -10 }}
className="p-10 rounded-[48px] bg-white/5 border border-white/10 backdrop-blur-xl group hover:bg-white/10 transition-all duration-300"
>
<div className="flex justify-between items-start mb-10">
<div className={`w-16 h-16 bg-white rounded-[20px] flex items-center justify-center text-3xl group-hover:rotate-12 transition-transform shadow-2xl`}>
{f.icon}
</div>
<span className="text-[10px] font-black text-white/30 tracking-[0.2em]">{f.id}</span>
</div>
<h3 className="text-2xl font-black mb-8">{f.name}</h3>
<ul className="space-y-4">
{f.items.map((item, j) => (
<li key={j} className="flex items-start gap-3 text-xs text-white/60 group-hover:text-white transition-colors tracking-tight font-medium">
<div className="w-1.5 h-1.5 bg-accent rounded-full mt-1 shrink-0"></div> {item}
</li>
))}
</ul>
</motion.div>
))}
</div>
</div>
</section>
);
};
const PricingSection = () => {
const { t } = useTranslation();
const [isAnnual, setIsAnnual] = useState(true);
const plans = [
{
name: t('pricing.starter.name'),
desc: t('pricing.starter.desc'),
monthlyPrice: 9,
annualPrice: 7,
features: t('pricing.starter.features', { returnObjects: true }) as string[],
cta: t('pricing.starter.cta'),
popular: false
},
{
name: t('pricing.pro.name'),
desc: t('pricing.pro.desc'),
monthlyPrice: 25,
annualPrice: 19,
features: t('pricing.pro.features', { returnObjects: true }) as string[],
cta: t('pricing.pro.cta'),
popular: true
},
{
name: t('pricing.business.name'),
desc: t('pricing.business.desc'),
monthlyPrice: 65,
annualPrice: 49,
features: t('pricing.business.features', { returnObjects: true }) as string[],
cta: t('pricing.business.cta'),
popular: false
}
];
return (
<section id="pricing" className="py-24 px-4 bg-white relative">
<div className="max-w-7xl mx-auto text-center">
<h2 className="text-4xl lg:text-6xl font-black text-primary mb-8 leading-tight">{t('pricing.title')}</h2>
<p className="text-gray-400 mb-16 text-lg font-medium max-w-2xl mx-auto">{t('pricing.subtitle')}</p>
<div className="flex items-center justify-center gap-4 mb-24 bg-gray-50 p-2 rounded-2xl w-fit mx-auto border border-gray-100">
<button
onClick={() => setIsAnnual(false)}
className={`px-10 py-3 rounded-xl font-bold text-sm transition-all ${!isAnnual ? 'bg-white shadow-lg text-primary' : 'text-gray-400'}`}
>
{t('pricing.monthly')}
</button>
<button
onClick={() => setIsAnnual(true)}
className={`px-10 py-3 rounded-xl font-bold text-sm transition-all relative ${isAnnual ? 'bg-white shadow-lg text-primary' : 'text-gray-400'}`}
>
{t('pricing.annual')}
<span className="absolute -top-3 -right-4 bg-accent text-primary px-2.5 py-1 rounded-full text-[9px] font-black uppercase shadow-sm">
-20%
</span>
</button>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 items-start">
{plans.map((p, i) => (
<motion.div
key={i}
whileHover={{ y: -10 }}
className={`p-12 rounded-[56px] border flex flex-col text-left transition-all duration-500 ${p.popular ? 'border-primary shadow-teal border-2 ring-8 ring-primary/5 scale-[1.05] bg-white relative z-10' : 'border-gray-50 bg-white hover:border-gray-100'}`}
>
{p.popular && (
<div className="absolute top-0 left-12 -translate-y-1/2 bg-primary text-white px-6 py-2 rounded-full text-[10px] font-black uppercase tracking-widest shadow-xl">
{t('pricing.bestValue')}
</div>
)}
<h3 className="text-2xl font-black text-primary mb-2">{p.name}</h3>
<p className="text-gray-400 text-xs font-bold mb-10 tracking-tight">{p.desc}</p>
<div className="mb-12">
<span className="text-6xl font-black text-primary tracking-tighter">{isAnnual ? p.annualPrice : p.monthlyPrice}</span>
<span className="text-gray-400 font-black ml-2 uppercase text-[10px] tracking-widest">{t('pricing.month')}</span>
</div>
<ul className="space-y-5 mb-12 flex-grow">
{p.features.map((f, j) => (
<li key={j} className="flex items-start gap-4 text-sm text-gray-500 font-bold leading-snug">
<CheckCircle2 size={18} className="text-secondary shrink-0 mt-0.5" /> {f}
</li>
))}
</ul>
<button className={`w-full py-6 rounded-3xl font-black text-lg transition-all active:scale-95 shadow-sm ${p.popular ? 'bg-primary text-white hover:bg-primary/95 shadow-primary/20 shadow-xl' : 'bg-gray-50 text-primary hover:bg-gray-100'}`}>
{p.cta}
</button>
</motion.div>
))}
</div>
<p className="mt-20 text-[10px] font-black text-gray-300 uppercase tracking-[0.3em]">
{t('pricing.footer')}
</p>
</div>
</section>
);
};
const CTASection = () => {
const { t } = useTranslation();
return (
<section className="py-24 px-4">
<div className="max-w-6xl mx-auto rounded-[80px] bg-gradient-to-br from-primary via-primary to-secondary p-12 lg:p-24 text-center text-white relative overflow-hidden shadow-teal shadow-2xl">
<div className="absolute top-0 left-0 w-full h-full opacity-30 pointer-events-none">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_20%_20%,#fff_0%,transparent_50%)] overflow-hidden"></div>
<div className="absolute inset-0 bg-[radial-gradient(circle_at_80%_80%,var(--color-accent)_0%,transparent_50%)]"></div>
</div>
<div className="relative z-10">
<h2 className="text-5xl lg:text-8xl font-black mb-10 leading-[0.9] tracking-tighter">
{t('cta.title')}
</h2>
<p className="text-xl opacity-80 mb-12 max-w-2xl mx-auto font-bold text-blue-50 leading-relaxed">
{t('cta.subtitle')}
</p>
<div className="flex flex-col sm:flex-row gap-6 justify-center items-center">
<button className="bg-accent text-primary px-14 py-6 rounded-[34px] text-xl font-black hover:scale-105 active:scale-95 transition-all shadow-2xl shadow-black/20">
{t('cta.button')}
</button>
<div className="flex items-center gap-3 text-xs font-black uppercase tracking-[0.2em] text-white/50">
<ShieldCheck size={16} /> {t('cta.secure')}
</div>
</div>
</div>
</div>
</section>
);
};
const Footer = () => {
const { t } = useTranslation();
return (
<footer className="bg-white border-t border-gray-100 pt-32 pb-12 px-4">
<div className="max-w-7xl mx-auto grid md:grid-cols-12 gap-16 mb-32 text-left">
<div className="md:col-span-4 lg:col-span-5">
<div className="flex items-center gap-3 mb-10">
<div className="w-10 h-10 bg-gradient-to-br from-primary to-secondary rounded-xl flex items-center justify-center text-white font-black">W</div>
<span className="text-2xl font-black tracking-tight text-primary">WORDLY<span className="text-secondary">.art</span></span>
</div>
<p className="text-gray-400 text-base font-medium leading-relaxed mb-10 max-w-xs">
{t('footer.desc')}
</p>
<div className="flex gap-4">
{[Globe, MessageSquare, Zap].map((Icon, i) => (
<div key={i} className="w-12 h-12 bg-gray-50 rounded-2xl flex items-center justify-center text-gray-300 hover:text-primary transition-all cursor-pointer border border-transparent hover:border-gray-100 shadow-sm">
<Icon size={20} />
</div>
))}
</div>
</div>
<div className="md:col-span-2 lg:col-span-2">
<h4 className="text-[10px] font-black uppercase tracking-[0.3em] text-primary mb-10">{t('footer.product')}</h4>
<ul className="space-y-5 text-sm text-gray-500 font-bold">
<li><a href="#features" className="hover:text-primary transition-colors">{t('nav.why')}</a></li>
<li><a href="#formats" className="hover:text-primary transition-colors">{t('nav.formats')}</a></li>
<li><a href="#pricing" className="hover:text-primary transition-colors">{t('nav.pricing')}</a></li>
<li><a href="#" className="hover:text-primary transition-colors">Integrations</a></li>
</ul>
</div>
<div className="md:col-span-3 lg:col-span-2">
<h4 className="text-[10px] font-black uppercase tracking-[0.3em] text-primary mb-10">{t('footer.resources')}</h4>
<ul className="space-y-5 text-sm text-gray-500 font-bold">
<li><a href="#" className="hover:text-primary transition-colors">Documentation</a></li>
<li><a href="#" className="hover:text-primary transition-colors">Security</a></li>
<li><a href="#" className="hover:text-primary transition-colors">Developer Portal</a></li>
<li><a href="#" className="hover:text-primary transition-colors">API Docs</a></li>
</ul>
</div>
<div className="md:col-span-3 lg:col-span-3">
<h4 className="text-[10px] font-black uppercase tracking-[0.3em] text-primary mb-10">{t('footer.legal')}</h4>
<ul className="space-y-5 text-sm text-gray-500 font-bold">
<li><a href="#" className="hover:text-primary transition-colors">Privacy Policy</a></li>
<li><a href="#" className="hover:text-primary transition-colors">Terms of Service</a></li>
<li><a href="#" className="hover:text-primary transition-colors">Cookie Policy</a></li>
<li><a href="#" className="hover:text-primary transition-colors">Contact</a></li>
</ul>
</div>
</div>
<div className="max-w-7xl mx-auto pt-10 border-t border-gray-100 flex flex-col md:flex-row justify-between items-center gap-8">
<div className="text-[10px] font-black text-gray-300 uppercase tracking-[0.3em]">
{t('footer.rights')}
</div>
<div className="flex items-center gap-10 text-[10px] font-black text-gray-400 uppercase tracking-[0.3em]">
<div className="flex items-center gap-3">
<span className="opacity-50">Translation engine</span>
<div className="flex gap-2">
<div className="w-1.5 h-1.5 bg-secondary rounded-full"></div>
<div className="w-1.5 h-1.5 bg-accent rounded-full"></div>
</div>
</div>
<span>v2.4.0</span>
</div>
</div>
</footer>
);
};
export default function App() {
return (
<div className="min-h-screen font-sans bg-white selection:bg-[#00A8A8]/30 overflow-x-hidden">
<Navbar />
<main>
<Hero />
<Steps />
<FeaturesContext />
<LayoutFeatures />
<FormatsSection />
<PricingSection />
<CTASection />
</main>
<Footer />
</div>
);
}

View File

@@ -0,0 +1,299 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'fr',
interpolation: {
escapeValue: false,
},
resources: {
fr: {
translation: {
nav: {
why: "Pourquoi nous ?",
formats: "Formats",
pricing: "Tarification",
login: "Connexion",
startFree: "Essai gratuit",
},
hero: {
tag: "IA Documentaire Professionnelle",
titleLine1: "Traduisez vos documents.",
titleLine2: "Mise en forme parfaite.",
description: "Le seul traducteur qui préserve les SmartArt, graphiques, tables des matières, formes, en-têtes et pieds de page — exactement tels qu'ils étaient.",
ctaMain: "Essai gratuit — 2 docs/mois",
ctaSec: "Voir les offres",
deleted: "Fichiers supprimés après 60 min",
noHidden: "Pas de prix cachés",
preview: "Aperçu avant paiement",
formattedOk: "Formatage OK",
aiActive: "Traduction IA active"
},
steps: {
title: "Comment ça marche ?",
subtitle: "Trois étapes. Zéro perte de format.",
step1: {
num: "01",
title: "Déposez votre fichier",
desc: "Glissez-déposez votre document Excel, Word, PowerPoint ou PDF."
},
step2: {
num: "02",
title: "Choisissez la langue",
desc: "Sélectionnez la langue cible et le moteur de traduction — classique ou IA contextuelle."
},
step3: {
num: "03",
title: "Téléchargez le résultat",
desc: "Récupérez votre document traduit avec un formatage identique à l'original."
}
},
features: {
tag: "Moteur de Traduction IA",
title: "La traduction qui comprend votre métier",
description: "Nos modèles IA analysent le contexte, respectent votre terminologie et traduisent même le texte dans vos images.",
context: {
title: "Contexte métier",
desc: "Décrivez votre domaine et obtenez des traductions adaptées, pas génériques."
},
glossary: {
title: "Glossaires métier",
desc: "Définissez vos termes techniques. CTA restera « Air Handling Unit », jamais « Call To Action »."
},
vision: {
title: "Vision sur images",
desc: "Le texte incrusté dans vos images, diagrammes et graphiques est détecté et traduit."
},
demo: {
source: "Source (FR)",
google: "Google Translate",
ours: "Notre IA"
}
},
layout: {
title: "Votre mise en forme,",
title2: "parfaitement préservée",
subtitle: "Les autres traducteurs détruisent votre mise en page. Pas nous.",
points: [
{ title: "SmartArt et diagrammes", desc: "Organigrammes, flux, hiérarchies — tout traduit à l'identique." },
{ title: "Tables des matières", desc: "Entrées de TDM, numéros et renvois mis à jour correctement." },
{ title: "Graphiques", desc: "Titres, étiquettes, légendes et séries — tout est traduit." },
{ title: "Formes et zones de texte", desc: "Rectangles, blocs arrondis, légendes — localisé partout." },
{ title: "En-têtes et pieds de page", desc: "En-têtes, pieds de page et notes ne sont jamais oubliés." },
{ title: "130+ langues", desc: "Google Traduction, DeepL et moteurs IA haute performance." }
]
},
formats: {
title: "Chaque format,",
title2: "chaque élément",
subtitle: "Nous traduisons ce que les autres oublient. Votre entreprise mérite une documentation irréprochable.",
word: {
name: "Word",
items: ["Paragraphes et titres", "Tableaux et graphiques", "Diagrammes SmartArt", "Table des matières", "En-têtes et pieds", "Formes et zones de texte", "Notes de bas de page"]
},
excel: {
name: "Excel",
items: ["Valeurs de cellules", "Noms de feuilles", "Graphiques et étiquettes", "En-têtes et pieds", "Cellules fusionnées"]
},
pptx: {
name: "PowerPoint",
items: ["Texte et notes", "Graphiques et diagrammes", "Formes et zones de texte", "Masques de diapositives", "Animations préservées"]
},
pdf: {
name: "PDF",
items: ["PDF textuels", "Mise en page préservée", "Images conservées", "Tableaux maintenus", "Sortie DOCX ou PDF"]
}
},
pricing: {
title: "Des prix simples et honnêtes",
subtitle: "Ce que vous voyez est ce que vous payez. Aucun frais caché.",
monthly: "Mensuel",
annual: "Annuel",
bestValue: "Le plus populaire",
month: "/mois",
footer: "Le prix affiché est le prix que vous payez. Aucun frais caché après traduction.",
starter: {
name: "Starter",
desc: "Pour les particuliers et petits projets",
features: ["50 documents / mois", "Jusqu'à 50 pages par doc", "Google Traduction + DeepL", "Fichiers jusqu'à 10 Mo"],
cta: "Commencer"
},
pro: {
name: "Pro",
desc: "Pour les professionnels exigeants",
features: ["200 documents / mois", "Jusqu'à 200 pages par doc", "Traduction par IA (DeepSeek)", "Google + DeepL inclus", "Glossaires et prompts", "Support prioritaire"],
cta: "Essayer Pro"
},
business: {
name: "Business",
desc: "Pour les équipes avec des besoins élevés",
features: ["1 000 documents / mois", "Jusqu'à 500 pages par doc", "IA Premium (Claude)", "Tous les fournisseurs + API", "Webhooks et automatisation", "5 postes d'équipe"],
cta: "Nous contacter"
}
},
cta: {
title: "Commencez à traduire en 30 secondes",
subtitle: "Aucune carte bancaire requise. Essayez gratuitement dès maintenant et redonnez vie à vos documents multilingues.",
button: "Créer un compte gratuit",
secure: "Sécurisé par chiffrement AES-256"
},
footer: {
desc: "Spécialiste de la traduction documentaire intelligente. Nous marions l'art de la mise en page et la science de l'IA contextuelle.",
product: "Produit",
resources: "Ressources",
legal: "Légal",
rights: "© 2026 Wordly.art — Tous droits réservés.",
hosted: "Hébergé en Europe",
powered: "Propulsé par"
}
}
},
en: {
translation: {
nav: {
why: "Why Us?",
formats: "Formats",
pricing: "Pricing",
login: "Log in",
startFree: "Start Free",
},
hero: {
tag: "Professional Document AI",
titleLine1: "Translate your documents.",
titleLine2: "Keep the formatting perfect.",
description: "The only translator that preserves SmartArt, charts, tables of contents, shapes, and complex layouts — exactly as they were.",
ctaMain: "Start Free — 2 docs/month",
ctaSec: "See Offers",
deleted: "Files deleted after 60 min",
noHidden: "No hidden fees",
preview: "Preview before payment",
formattedOk: "Formatting OK",
aiActive: "AI Translation active"
},
steps: {
title: "How it works?",
subtitle: "Three steps. Zero formatting loss.",
step1: {
num: "01",
title: "Upload your file",
desc: "Drag & drop your Excel, Word, PowerPoint or PDF document."
},
step2: {
num: "02",
title: "Pick language & engine",
desc: "Select target language and engine — classic or context-aware AI."
},
step3: {
num: "03",
title: "Download the result",
desc: "Get your translated document with formatting identical to the original."
}
},
features: {
tag: "AI Translation Engine",
title: "Translation that understands your craft",
description: "Our AI models analyze context, respect your terminology, and even translate text inside images.",
context: {
title: "Industry Context",
desc: "Describe your field and get tailored translations, not generic ones."
},
glossary: {
title: "Industry Glossaries",
desc: "Define your technical terms. CTA stay 'Air Handling Unit', never 'Call To Action'."
},
vision: {
title: "Image Vision",
desc: "Text embedded in images, diagrams and charts is detected and translated."
},
demo: {
source: "Source (FR)",
google: "Google Translate",
ours: "Our AI"
}
},
layout: {
title: "Your formatting,",
title2: "perfectly preserved",
subtitle: "Other translators break your layout. We don't.",
points: [
{ title: "SmartArt & Diagrams", desc: "Org charts, flowcharts, hierarchies — all translated identically." },
{ title: "Tables of Contents", desc: "TOC entries, page numbers and cross-references updated correctly." },
{ title: "Charts & Graphs", desc: "Titles, axis labels, legends and series names — everything is translated." },
{ title: "Shapes & Text Boxes", desc: "Rectangles, rounded blocks, callouts — localized everywhere." },
{ title: "Headers & Footers", desc: "Headers, footers and footnotes are never missed." },
{ title: "130+ Languages", desc: "Google Translate, DeepL and professional-grade AI engines." }
]
},
formats: {
title: "Every format,",
title2: "every element",
subtitle: "We translate what others miss. Your business deserves irreproachable documentation.",
word: {
name: "Word",
items: ["Paragraphs & headings", "Tables & charts", "SmartArt diagrams", "Table of contents", "Headers & footers", "Shapes & text boxes", "Footnotes & endnotes"]
},
excel: {
name: "Excel",
items: ["Cell values", "Sheet names", "Charts & labels", "Headers & footers", "Merged cells preserved"]
},
pptx: {
name: "PowerPoint",
items: ["Slide text & notes", "Charts & diagrams", "Shapes & text boxes", "Master layouts", "Animations preserved"]
},
pdf: {
name: "PDF",
items: ["Text-based PDFs", "Layout preserved", "Images kept in place", "Tables maintained", "Output as DOCX or PDF"]
}
},
pricing: {
title: "Simple, honest pricing",
subtitle: "What you see is what you pay. No hidden fees.",
monthly: "Monthly",
annual: "Annual",
bestValue: "Most Popular",
month: "/month",
footer: "What you see is what you pay. No hidden fees after translation.",
starter: {
name: "Starter",
desc: "For individuals and small projects",
features: ["50 documents / month", "Up to 50 pages per doc", "Google Translate + DeepL", "Files up to 10 MB"],
cta: "Get Started"
},
pro: {
name: "Pro",
desc: "For demanding professionals",
features: ["200 documents / month", "Up to 200 pages per doc", "AI-powered translation (DeepSeek)", "Google + DeepL included", "Custom glossaries & prompts", "Priority support"],
cta: "Try Pro"
},
business: {
name: "Business",
desc: "For teams with high-volume needs",
features: ["1,000 documents / month", "Up to 500 pages per doc", "Premium AI (Claude)", "All providers + API access", "Webhooks & automation", "5 team seats"],
cta: "Contact Us"
}
},
cta: {
title: "Start translating in 30 seconds",
subtitle: "No credit card required. Try for free now and bring your multilingual documents back to life.",
button: "Create Free Account",
secure: "Secured by AES-256 encryption"
},
footer: {
desc: "Expert in intelligent document translation. We blend the art of layout with the science of contextual AI.",
product: "Product",
resources: "Resources",
legal: "Legal",
rights: "© 2026 Wordly.art — All rights reserved.",
hosted: "Hosted in Europe",
powered: "Powered by"
}
}
}
}
});
export default i18n;

View File

@@ -0,0 +1,38 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&family=Space+Grotesk:wght@400;500;600;700&display=swap');
@import "tailwindcss";
@theme {
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
--font-display: "Space Grotesk", sans-serif;
--color-primary: #003B5C;
--color-secondary: #00D1C1;
--color-accent: #C6F432;
--shadow-warm: 0 20px 50px -12px rgba(0, 59, 92, 0.12);
--shadow-teal: 0 20px 50px -12px rgba(0, 209, 193, 0.15);
}
@layer base {
body {
@apply font-sans selection:bg-secondary/30 text-primary;
}
h1, h2, h3, h4 {
@apply font-display tracking-tight;
}
}
.glass {
@apply bg-white/70 backdrop-blur-xl border border-white/20;
}
.gradient-text {
@apply bg-clip-text text-transparent bg-gradient-to-r from-primary via-secondary to-accent;
}
.bento-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}

View File

@@ -0,0 +1,11 @@
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import './i18n';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);