fix(ui): wave 2 from critique re-run — AA contrast, unified PageHeader, checkout confirm
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m46s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m46s
Nav: teams/settings/services back out of the nav until product-ready (user decision); teams page was already half-finished (UUID member display is a backend limitation). Payment: explicit confirmation dialog (plan, amount, billing period, Stripe note) before any redirect; ?plan= URL now pre-opens the dialog instead of triggering a silent checkout. Contrast AA sweep (71 replacements): functional micro-labels raised to >=60-65% opacity and >=10px across sidebar, header, pricing, landing; decorative all-caps 'interface' label removed. Design unification: new PageHeader component (accent pill + serif base/accent title) applied to settings, services, reviews, teams; pricing header returned to the editorial voice (serif + accent pill); dead GlossaryCard deleted. i18n residuals: suggestion chips, 'Standard' provider label, notification close, model-combobox strings extracted (+broken bg-surface/border-border- subtle tokens fixed); ~45 new keys EN+FR; 13 dead keys removed; zero missing keys verified. Reviews: icon-only row actions now carry visible text; 'Approve all' is two-step armed-confirm. Accelerators: Ctrl/Cmd+Enter submits; arrow-key navigation in the language combobox; recent-jobs history cap 8->20 with per-job download; source=target config rejected; empty 'Master Quality' badge removed. Cookie consent: reopenable via footer link, emoji replaced with drawn icon. Verified: build exit 0, vitest 9/9, eslint 64 errors = previous level, 0 missing i18n keys.
This commit is contained in:
@@ -106,7 +106,7 @@ const Hero = () => {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
>
|
||||
<div className="inline-flex items-center gap-2 px-4 py-1.5 mb-10 text-[10px] font-bold tracking-[0.3em] text-brand-dark/40 dark:text-white/40 uppercase bg-transparent border border-black/10 dark:border-white/10 rounded-full">
|
||||
<div className="inline-flex items-center gap-2 px-4 py-1.5 mb-10 text-[10px] font-bold tracking-[0.3em] text-brand-dark/60 dark:text-white/60 uppercase bg-transparent border border-black/10 dark:border-white/10 rounded-full">
|
||||
<Languages size={10} /> {t('landing.hero.tag')}
|
||||
</div>
|
||||
<h1 className="text-6xl md:text-8xl mb-10 text-brand-dark dark:text-white font-serif font-medium tracking-tight leading-[1.1]">
|
||||
@@ -187,7 +187,7 @@ const Steps = () => {
|
||||
<div className="mb-24">
|
||||
<span className="accent-pill mb-6 block w-fit">{t('landing.steps.process')}</span>
|
||||
<h2 className="text-4xl md:text-5xl text-brand-dark dark:text-white font-serif font-medium tracking-tight mb-6">{t('landing.steps.title')}</h2>
|
||||
<p className="text-brand-dark/40 dark:text-white/40 text-lg max-w-xl font-light">{t('landing.steps.subtitle')}</p>
|
||||
<p className="text-brand-dark/60 dark:text-white/60 text-lg max-w-xl font-light">{t('landing.steps.subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-16">
|
||||
@@ -197,7 +197,7 @@ const Steps = () => {
|
||||
{st.icon}
|
||||
</div>
|
||||
<h3 className="text-xl font-serif font-medium text-brand-dark dark:text-white mb-4">{st.title}</h3>
|
||||
<p className="text-brand-dark/40 dark:text-white/40 leading-relaxed font-light">{st.desc}</p>
|
||||
<p className="text-brand-dark/60 dark:text-white/60 leading-relaxed font-light">{st.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -222,9 +222,9 @@ const FeaturesContext = () => {
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{[
|
||||
{ icon: <Monitor className="text-brand-dark/40 dark:text-white/40" />, title: t('landing.features.context.title'), desc: t('landing.features.context.desc') },
|
||||
{ icon: <Layers className="text-brand-dark/40 dark:text-white/40" />, title: t('landing.features.glossary.title'), desc: t('landing.features.glossary.desc') },
|
||||
{ icon: <Eye className="text-brand-dark/40 dark:text-white/40" />, title: t('landing.features.vision.title'), desc: t('landing.features.vision.desc') },
|
||||
{ icon: <Monitor className="text-brand-dark/60 dark:text-white/60" />, title: t('landing.features.context.title'), desc: t('landing.features.context.desc') },
|
||||
{ icon: <Layers className="text-brand-dark/60 dark:text-white/60" />, title: t('landing.features.glossary.title'), desc: t('landing.features.glossary.desc') },
|
||||
{ icon: <Eye className="text-brand-dark/60 dark:text-white/60" />, title: t('landing.features.vision.title'), desc: t('landing.features.vision.desc') },
|
||||
].map((item, i) => (
|
||||
<div key={i} className="editorial-card p-10 flex flex-col items-start gap-8">
|
||||
<div className="w-10 h-10 border border-black/5 dark:border-white/10 rounded-full flex items-center justify-center">
|
||||
@@ -232,7 +232,7 @@ const FeaturesContext = () => {
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-xl font-serif font-medium mb-4 text-brand-dark dark:text-white">{item.title}</h4>
|
||||
<p className="text-brand-dark/40 dark:text-white/40 leading-relaxed font-light text-sm">{item.desc}</p>
|
||||
<p className="text-brand-dark/60 dark:text-white/60 leading-relaxed font-light text-sm">{item.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -283,10 +283,10 @@ const LayoutFeatures = () => {
|
||||
const FormatsSection = () => {
|
||||
const { t } = useI18n();
|
||||
const formats = [
|
||||
{ id: ".DOCX", name: t('landing.formats.word.name'), icon: <FileText className="text-brand-dark/40 dark:text-white/40" />, items: [t('landing.formats.word.i1'), t('landing.formats.word.i2'), t('landing.formats.word.i3'), t('landing.formats.word.i4')] },
|
||||
{ id: ".XLSX", name: t('landing.formats.excel.name'), icon: <FileSpreadsheet className="text-brand-dark/40 dark:text-white/40" />, items: [t('landing.formats.excel.i1'), t('landing.formats.excel.i2'), t('landing.formats.excel.i3'), t('landing.formats.excel.i4')] },
|
||||
{ id: ".PPTX", name: t('landing.formats.pptx.name'), icon: <Presentation className="text-brand-dark/40 dark:text-white/40" />, items: [t('landing.formats.pptx.i1'), t('landing.formats.pptx.i2'), t('landing.formats.pptx.i3'), t('landing.formats.pptx.i4')] },
|
||||
{ id: ".PDF", name: t('landing.formats.pdf.name'), icon: <FileText className="text-brand-dark/40 dark:text-white/40" />, items: [t('landing.formats.pdf.i1'), t('landing.formats.pdf.i2'), t('landing.formats.pdf.i3'), t('landing.formats.pdf.i4')] },
|
||||
{ id: ".DOCX", name: t('landing.formats.word.name'), icon: <FileText className="text-brand-dark/60 dark:text-white/60" />, items: [t('landing.formats.word.i1'), t('landing.formats.word.i2'), t('landing.formats.word.i3'), t('landing.formats.word.i4')] },
|
||||
{ id: ".XLSX", name: t('landing.formats.excel.name'), icon: <FileSpreadsheet className="text-brand-dark/60 dark:text-white/60" />, items: [t('landing.formats.excel.i1'), t('landing.formats.excel.i2'), t('landing.formats.excel.i3'), t('landing.formats.excel.i4')] },
|
||||
{ id: ".PPTX", name: t('landing.formats.pptx.name'), icon: <Presentation className="text-brand-dark/60 dark:text-white/60" />, items: [t('landing.formats.pptx.i1'), t('landing.formats.pptx.i2'), t('landing.formats.pptx.i3'), t('landing.formats.pptx.i4')] },
|
||||
{ id: ".PDF", name: t('landing.formats.pdf.name'), icon: <FileText className="text-brand-dark/60 dark:text-white/60" />, items: [t('landing.formats.pdf.i1'), t('landing.formats.pdf.i2'), t('landing.formats.pdf.i3'), t('landing.formats.pdf.i4')] },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -295,7 +295,7 @@ const FormatsSection = () => {
|
||||
<div className="mb-24">
|
||||
<span className="accent-pill mb-6 block w-fit">{t('landing.formats.pill')}</span>
|
||||
<h2 className="text-4xl md:text-5xl text-brand-dark dark:text-white mb-6 leading-tight font-serif font-medium tracking-tight">{t('landing.formats.title')} <span className="italic">{t('landing.formats.title2')}</span></h2>
|
||||
<p className="text-brand-dark/40 dark:text-white/40 max-w-xl text-lg font-light">{t('landing.formats.subtitle')}</p>
|
||||
<p className="text-brand-dark/60 dark:text-white/60 max-w-xl text-lg font-light">{t('landing.formats.subtitle')}</p>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{formats.map((f, i) => (
|
||||
@@ -304,12 +304,12 @@ const FormatsSection = () => {
|
||||
<div className="w-12 h-12 bg-brand-muted dark:bg-[#1f1f1f] rounded-xl flex items-center justify-center text-xl group-hover:bg-brand-dark dark:group-hover:bg-brand-accent group-hover:text-white dark:group-hover:text-brand-dark transition-all">
|
||||
{f.icon}
|
||||
</div>
|
||||
<span className="text-[9px] font-bold text-brand-dark/20 dark:text-white/20 uppercase tracking-[0.2em]">{f.id}</span>
|
||||
<span className="text-[9px] font-bold text-brand-dark/50 dark:text-white/50 uppercase tracking-[0.2em]">{f.id}</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-serif font-medium mb-6 text-brand-dark dark:text-white tracking-tight">{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-brand-dark/40 dark:text-white/40 font-medium">
|
||||
<li key={j} className="flex items-start gap-3 text-xs text-brand-dark/60 dark:text-white/60 font-medium">
|
||||
<div className="w-1 h-1 bg-brand-accent rounded-full mt-1.5 shrink-0 opacity-40"></div> {item}
|
||||
</li>
|
||||
))}
|
||||
@@ -337,14 +337,14 @@ const PricingSection = () => {
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="text-center mb-24">
|
||||
<h2 className="text-4xl md:text-5xl mb-8 leading-tight text-brand-dark dark:text-white font-serif font-medium tracking-tight">{t('landing.pricing.title')}</h2>
|
||||
<p className="text-brand-dark/40 dark:text-white/40 text-xl font-light max-w-2xl mx-auto">{t('landing.pricing.subtitle')}</p>
|
||||
<p className="text-brand-dark/60 dark:text-white/60 text-xl font-light max-w-2xl mx-auto">{t('landing.pricing.subtitle')}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-12 mb-32">
|
||||
<button onClick={() => setIsAnnual(false)} className={`text-sm font-bold uppercase tracking-widest transition-all ${!isAnnual ? 'text-brand-dark dark:text-white underline underline-offset-8' : 'text-brand-dark/30 dark:text-white/30 hover:text-brand-dark dark:hover:text-white'}`}>
|
||||
<button onClick={() => setIsAnnual(false)} className={`text-sm font-bold uppercase tracking-widest transition-all ${!isAnnual ? 'text-brand-dark dark:text-white underline underline-offset-8' : 'text-brand-dark/55 dark:text-white/55 hover:text-brand-dark dark:hover:text-white'}`}>
|
||||
{t('landing.pricing.monthly')}
|
||||
</button>
|
||||
<button onClick={() => setIsAnnual(true)} className={`text-sm font-bold uppercase tracking-widest transition-all relative ${isAnnual ? 'text-brand-dark dark:text-white underline underline-offset-8' : 'text-brand-dark/30 dark:text-white/30 hover:text-brand-dark dark:hover:text-white'}`}>
|
||||
<button onClick={() => setIsAnnual(true)} className={`text-sm font-bold uppercase tracking-widest transition-all relative ${isAnnual ? 'text-brand-dark dark:text-white underline underline-offset-8' : 'text-brand-dark/55 dark:text-white/55 hover:text-brand-dark dark:hover:text-white'}`}>
|
||||
{t('landing.pricing.annual')}
|
||||
<span className="absolute -top-6 left-1/2 -translate-x-1/2 text-[9px] font-bold text-brand-accent whitespace-nowrap italic">
|
||||
(-20%)
|
||||
@@ -356,11 +356,11 @@ const PricingSection = () => {
|
||||
{plans.map((p, i) => (
|
||||
<div key={i} className="p-16 bg-white dark:bg-[#141414] flex flex-col text-left transition-all duration-500 hover:z-10 hover:shadow-2xl hover:scale-[1.01]">
|
||||
<h3 className="text-2xl font-serif font-medium mb-2 text-brand-dark dark:text-white tracking-tight">{p.name}</h3>
|
||||
<p className="text-brand-dark/40 dark:text-white/40 text-xs font-light mb-14 tracking-widest uppercase">{p.desc}</p>
|
||||
<p className="text-brand-dark/60 dark:text-white/60 text-xs font-light mb-14 tracking-widest uppercase">{p.desc}</p>
|
||||
|
||||
<div className="mb-14">
|
||||
<span className="text-5xl font-serif font-medium leading-none text-brand-dark dark:text-white">€{isAnnual ? p.annualPrice : p.monthlyPrice}</span>
|
||||
<span className="text-brand-dark/30 dark:text-white/30 font-light ml-2 uppercase text-[10px] tracking-[0.2em]">/ {t('landing.pricing.month')}</span>
|
||||
<span className="text-brand-dark/55 dark:text-white/55 font-light ml-2 uppercase text-[10px] tracking-[0.2em]">/ {t('landing.pricing.month')}</span>
|
||||
</div>
|
||||
|
||||
<ul className="space-y-6 mb-16 flex-grow">
|
||||
@@ -381,7 +381,7 @@ const PricingSection = () => {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="text-center mt-12 text-brand-dark/20 dark:text-white/20 text-xs font-bold uppercase tracking-widest">{t('landing.pricing.footer')}</p>
|
||||
<p className="text-center mt-12 text-brand-dark/50 dark:text-white/50 text-xs font-bold uppercase tracking-widest">{t('landing.pricing.footer')}</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
@@ -395,7 +395,7 @@ const CTASection = () => {
|
||||
<h2 className="text-5xl md:text-7xl text-brand-dark dark:text-white mb-10 leading-tight tracking-tight font-serif font-medium">
|
||||
{t('landing.cta.title')}
|
||||
</h2>
|
||||
<p className="text-lg text-brand-dark/40 dark:text-white/40 mb-16 font-light">
|
||||
<p className="text-lg text-brand-dark/60 dark:text-white/60 mb-16 font-light">
|
||||
{t('landing.cta.subtitle')}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-6 justify-center items-center">
|
||||
@@ -416,14 +416,14 @@ const Footer = () => {
|
||||
<div className="grid md:grid-cols-12 gap-20 mb-40">
|
||||
<div className="md:col-span-12 lg:col-span-6">
|
||||
<Logo />
|
||||
<p className="text-brand-dark/40 dark:text-white/40 text-lg font-medium mt-12 mb-12 max-w-sm leading-relaxed uppercase tracking-widest">
|
||||
<p className="text-brand-dark/60 dark:text-white/60 text-lg font-medium mt-12 mb-12 max-w-sm leading-relaxed uppercase tracking-widest">
|
||||
{t('landing.footer.desc')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-4 lg:col-span-2">
|
||||
<h4 className="text-[10px] font-bold uppercase tracking-[0.3em] text-brand-dark dark:text-white mb-10">{t('landing.footer.product')}</h4>
|
||||
<ul className="space-y-6 text-xs text-brand-dark/40 dark:text-white/40 font-bold uppercase tracking-widest leading-none">
|
||||
<ul className="space-y-6 text-xs text-brand-dark/60 dark:text-white/60 font-bold uppercase tracking-widest leading-none">
|
||||
<li><a href="#features" className="hover:text-brand-dark dark:hover:text-white transition-colors">{t('landing.nav.why')}</a></li>
|
||||
<li><a href="#formats" className="hover:text-brand-dark dark:hover:text-white transition-colors">{t('landing.nav.formats')}</a></li>
|
||||
<li><a href="#pricing" className="hover:text-brand-dark dark:hover:text-white transition-colors">{t('landing.nav.pricing')}</a></li>
|
||||
@@ -433,11 +433,20 @@ const Footer = () => {
|
||||
</div>
|
||||
|
||||
<div className="pt-10 border-t border-black/5 dark:border-white/5 flex flex-col md:flex-row justify-between items-center gap-10">
|
||||
<div className="text-[10px] font-bold text-brand-dark/20 dark:text-white/20 uppercase tracking-[0.4em]">
|
||||
<div className="text-[10px] font-bold text-brand-dark/50 dark:text-white/50 uppercase tracking-[0.4em]">
|
||||
{t('landing.footer.rights')}
|
||||
</div>
|
||||
<div className="text-[10px] font-mono text-brand-dark/30 dark:text-white/30 uppercase tracking-widest">
|
||||
wordly.art
|
||||
<div className="flex items-center gap-6">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => import('@/components/ui/cookie-consent').then(m => m.reopenCookieConsent())}
|
||||
className="text-[10px] font-bold text-brand-dark/50 dark:text-white/50 uppercase tracking-[0.2em] hover:text-brand-dark dark:hover:text-white transition-colors"
|
||||
>
|
||||
{t('cookieConsent.reopen')}
|
||||
</button>
|
||||
<div className="text-[10px] font-mono text-brand-dark/55 dark:text-white/55 uppercase tracking-widest">
|
||||
wordly.art
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user