feat(translate): refonte du design de la page de traduction et du sélecteur de moteurs (Etapes 1-3)
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m12s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m12s
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
Zap, CheckCircle2,
|
||||
Search, Languages, Wrench, Activity, Timer,
|
||||
Download, AlertTriangle, FileType,
|
||||
Image as ImageIcon,
|
||||
} from 'lucide-react';
|
||||
import { useFileUpload } from './useFileUpload';
|
||||
import { useTranslationConfig } from './useTranslationConfig';
|
||||
@@ -14,6 +15,7 @@ import { useTranslationSubmit } from './useTranslationSubmit';
|
||||
import LanguageSelector from './LanguageSelector';
|
||||
import { ProviderSelector } from './ProviderSelector';
|
||||
import { GlossarySelector } from './GlossarySelector';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { useNotification } from '@/components/ui/notification';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { API_BASE } from '@/lib/config';
|
||||
@@ -181,86 +183,96 @@ export default function TranslatePage() {
|
||||
const activeStepIdx = getActiveStepIdx(submit.progress);
|
||||
const qualityLabel = useMemo(() => getQualityLabel(t, config.provider), [t, config.provider]);
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════ */
|
||||
/* EDITORIAL LAYOUT */
|
||||
/* ═══════════════════════════════════════════════════════════════ */
|
||||
return (
|
||||
<div className="min-h-full p-6 lg:p-8 dark:bg-[#0a0a0a]">
|
||||
<div className="min-h-full p-6 lg:p-8 dark:bg-[#0a0a0a] selection:bg-brand-accent/10">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
|
||||
{/* ── HEADER ────────────────────────────────────────────── */}
|
||||
{/* ── HEADER (Landing Page Style) ───────────────────────── */}
|
||||
<div className="mb-12">
|
||||
{showProcessing ? (
|
||||
<>
|
||||
<span className="accent-pill mb-4 block w-fit italic">{t('landing.translate.processing')}</span>
|
||||
<h1 className="text-5xl font-black uppercase tracking-tighter mb-4 leading-none text-brand-dark dark:text-white">
|
||||
{t('landing.translate.aiAnalysis')}
|
||||
<span className="accent-pill mb-4 block w-fit italic">Traitement en cours</span>
|
||||
<h1 className="text-4xl md:text-5xl mb-3 leading-tight text-brand-dark dark:text-white font-serif font-medium tracking-tight">
|
||||
Analyse IA <span className="italic">Active</span>
|
||||
</h1>
|
||||
<p className="text-brand-dark/40 font-medium dark:text-white/40">
|
||||
{t('landing.translate.preservingLayout')}
|
||||
<p className="text-brand-dark/50 dark:text-white/50 text-sm font-light leading-relaxed">
|
||||
Votre mise en page est en cours de préservation par notre moteur contextuel.
|
||||
</p>
|
||||
</>
|
||||
) : showComplete ? (
|
||||
<>
|
||||
<span className="accent-pill mb-4 block w-fit italic">{t('dashboard.translate.completed')}</span>
|
||||
<h1 className="text-5xl font-black uppercase tracking-tighter mb-4 leading-none text-brand-dark dark:text-white">
|
||||
{t('dashboard.translate.completed')}
|
||||
<span className="accent-pill mb-4 block w-fit italic">Complété</span>
|
||||
<h1 className="text-4xl md:text-5xl mb-3 leading-tight text-brand-dark dark:text-white font-serif font-medium tracking-tight">
|
||||
Traduction <span className="italic">terminée</span>
|
||||
</h1>
|
||||
<p className="text-brand-dark/40 font-medium dark:text-white/40">
|
||||
<p className="text-brand-dark/50 dark:text-white/50 text-sm font-light leading-relaxed truncate max-w-xl">
|
||||
{submit.fileName}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="accent-pill mb-4 block w-fit">{t('landing.translate.newProject')}</span>
|
||||
<h1 className="text-5xl font-black uppercase tracking-tighter mb-4 leading-none text-brand-dark dark:text-white">
|
||||
{t('landing.translate.title')}
|
||||
<span className="accent-pill mb-4 block w-fit">Espace Pro</span>
|
||||
<h1 className="text-4xl md:text-5xl mb-3 leading-tight text-brand-dark dark:text-white font-serif font-medium tracking-tight">
|
||||
Traduire un <span className="italic">document</span>
|
||||
</h1>
|
||||
<p className="text-brand-dark/40 font-medium dark:text-white/40">
|
||||
{t('landing.translate.subtitle')}
|
||||
<p className="text-brand-dark/50 dark:text-white/50 text-sm font-light leading-relaxed">
|
||||
Conservez la mise en page d'origine grâce au moteur de traduction ultra-haute fidélité.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ── GRID: 8/4 SPLIT ───────────────────────────────────── */}
|
||||
<div className="grid lg:grid-cols-12 gap-12">
|
||||
{/* ── GRID: 7/5 SPLIT ───────────────────────────────────── */}
|
||||
<div className="grid lg:grid-cols-12 gap-8 items-start">
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════ */}
|
||||
{/* LEFT (8 cols) — content swaps based on state */}
|
||||
{/* LEFT (7 cols) — content swaps based on state */}
|
||||
{/* ═══════════════════════════════════════════════════════ */}
|
||||
<div className="lg:col-span-8">
|
||||
<div className="lg:col-span-7 space-y-6">
|
||||
|
||||
{/* ── UPLOAD STATE: Editorial Dropzone ──────────────── */}
|
||||
{showUpload && (
|
||||
<div
|
||||
className="bg-white border-2 border-dashed border-brand-accent/20 rounded-[40px] p-20 flex flex-col items-center justify-center text-center group cursor-pointer hover:border-brand-accent transition-all shadow-editorial dark:bg-[#141414] dark:border-white/10"
|
||||
className="bg-white border-2 border-dashed border-brand-accent/15 dark:border-white/10 rounded-[32px] p-12 flex flex-col items-center justify-center text-center group cursor-pointer hover:border-brand-accent/40 dark:hover:border-brand-accent/40 hover:bg-brand-muted/10 dark:hover:bg-brand-muted/5 transition-all shadow-editorial dark:bg-[#141414]"
|
||||
onDragOver={upload.handleDragOver}
|
||||
onDragLeave={upload.handleDragLeave}
|
||||
onDrop={upload.handleDrop}
|
||||
onClick={() => dropzoneInputRef.current?.click()}
|
||||
>
|
||||
<div className="w-20 h-20 bg-brand-muted rounded-3xl flex items-center justify-center text-brand-accent group-hover:scale-110 group-hover:bg-brand-dark group-hover:text-white transition-all mb-8 shadow-sm dark:bg-white/10">
|
||||
<Upload size={32} />
|
||||
<div className="absolute top-4 right-4 text-[8px] font-bold uppercase tracking-widest text-brand-dark/30 dark:text-white/30 bg-brand-muted dark:bg-white/5 px-3 py-1 rounded-full border border-black/[0.03] dark:border-white/[0.03]">
|
||||
Format natif
|
||||
</div>
|
||||
<h3 className="text-2xl font-black uppercase tracking-tight mb-4 text-brand-dark dark:text-white">
|
||||
|
||||
<div className="w-16 h-16 bg-brand-muted dark:bg-white/5 rounded-2xl flex items-center justify-center text-brand-accent group-hover:scale-105 group-hover:bg-brand-dark dark:group-hover:bg-brand-accent group-hover:text-white dark:group-hover:text-brand-dark transition-all duration-300 mb-6 shadow-sm">
|
||||
<Upload size={24} />
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-bold tracking-tight mb-2 text-brand-dark dark:text-white uppercase">
|
||||
{t('landing.translate.dropHere')}
|
||||
</h3>
|
||||
<p className="text-sm text-brand-dark/40 mb-12 font-medium dark:text-white/40">
|
||||
<p className="text-xs text-brand-dark/40 dark:text-white/40 mb-8 font-medium">
|
||||
{t('landing.translate.supportedFormats')}
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
|
||||
{/* Simulated file triggers */}
|
||||
<div className="flex flex-wrap justify-center gap-2.5" onClick={(e) => e.stopPropagation()}>
|
||||
{[
|
||||
{ label: 'Word', icon: <FileText size={12} className="text-blue-500" /> },
|
||||
{ label: 'Excel', icon: <FileSpreadsheet size={12} className="text-green-500" /> },
|
||||
{ label: 'Slides', icon: <Presentation size={12} className="text-orange-500" /> },
|
||||
{ label: 'PDF', icon: <FileType size={12} className="text-red-500" /> },
|
||||
{ label: 'Word (.docx)', type: 'word' as const, icon: <FileText size={11} className="text-blue-500" /> },
|
||||
{ label: 'Excel (.xlsx)', type: 'excel' as const, icon: <FileSpreadsheet size={11} className="text-green-500" /> },
|
||||
{ label: 'Slides (.pptx)', type: 'slides' as const, icon: <Presentation size={11} className="text-orange-500" /> },
|
||||
{ label: 'PDF (.pdf)', type: 'pdf' as const, icon: <FileType size={11} className="text-red-500" /> },
|
||||
].map(f => (
|
||||
<span key={f.label} className="flex items-center gap-3 px-4 py-2 bg-brand-muted rounded-xl text-[10px] font-black uppercase tracking-widest text-brand-dark/60 border border-transparent hover:border-brand-accent/30 transition-all dark:bg-white/10 dark:text-white/60">
|
||||
<button
|
||||
key={f.type}
|
||||
type="button"
|
||||
onClick={() => upload.setMockFile(f.type)}
|
||||
className="flex items-center gap-2 px-3.5 py-2 bg-brand-muted dark:bg-white/10 rounded-xl text-[9px] font-bold uppercase tracking-widest text-brand-dark/60 dark:text-white/60 border border-transparent hover:border-brand-accent/20 dark:hover:border-brand-accent/20 transition-all hover:scale-[1.02] active:scale-[0.98]"
|
||||
>
|
||||
{f.icon} {f.label}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Hidden file input for click-to-upload */}
|
||||
<input
|
||||
ref={dropzoneInputRef}
|
||||
@@ -272,37 +284,37 @@ export default function TranslatePage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── CONFIGURING STATE: File strip ─────────────────── */}
|
||||
{/* ── CONFIGURING STATE: File indicator ──────────────── */}
|
||||
{showConfiguring && (
|
||||
<div className="editorial-card p-10 bg-white border-none shadow-editorial dark:bg-[#141414]">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-[0.3em] mb-10 text-brand-dark/50 border-b border-black/5 pb-6 dark:text-white/50 dark:border-white/5">
|
||||
{t('landing.translate.sourceDocument')}
|
||||
<div className="editorial-card p-8 bg-white border-none shadow-editorial dark:bg-[#141414] space-y-6">
|
||||
<h4 className="text-[10px] font-bold uppercase tracking-[0.2em] text-brand-dark/30 dark:text-white/30 border-b border-black/5 dark:border-white/5 pb-4">
|
||||
{t('landing.translate.sourceDocument') || 'Document Source'}
|
||||
</h4>
|
||||
<FileStrip file={upload.file!} onRemove={upload.removeFile} onReplace={() => replaceInputRef.current?.click()} t={t} />
|
||||
<input ref={replaceInputRef} type="file" accept=".xlsx,.docx,.pptx,.pdf" className="hidden" onChange={upload.handleFileSelect} />
|
||||
{upload.error && <p className="mt-2 text-sm text-destructive">{upload.error}</p>}
|
||||
{upload.error && <p className="mt-2 text-xs text-destructive">{upload.error}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── PROCESSING STATE: Rich progress ───────────────── */}
|
||||
{showProcessing && (
|
||||
<div className="editorial-card p-16 h-full border-none shadow-editorial bg-white dark:bg-[#141414]">
|
||||
<div className="flex items-center gap-6 mb-20">
|
||||
<div className="w-16 h-16 bg-brand-muted rounded-2xl flex items-center justify-center text-brand-accent border border-brand-accent/10 animate-pulse dark:bg-white/10 dark:border-white/10">
|
||||
<div className="editorial-card p-12 h-full border-none shadow-editorial bg-white dark:bg-[#141414] space-y-12">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="w-16 h-16 bg-brand-muted dark:bg-white/5 rounded-2xl flex items-center justify-center text-brand-accent border border-brand-accent/10 animate-pulse">
|
||||
<Activity size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-black uppercase tracking-tight mb-2 text-brand-dark dark:text-white">
|
||||
{t('dashboard.translate.translating')}
|
||||
<div className="text-left">
|
||||
<h3 className="text-2xl font-serif font-medium text-brand-dark dark:text-white tracking-tight">
|
||||
Moteur contextuel actif
|
||||
</h3>
|
||||
<p className="text-[10px] text-brand-dark/50 font-black uppercase tracking-widest dark:text-white/50">
|
||||
<p className="text-[10px] text-brand-dark/30 dark:text-white/30 font-bold uppercase tracking-widest mt-1">
|
||||
{submit.fileName || upload.file?.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Line with step icons */}
|
||||
<div className="relative h-2 bg-brand-muted rounded-full mb-24 dark:bg-white/10">
|
||||
<div className="relative h-2 bg-brand-muted dark:bg-white/5 rounded-full my-12">
|
||||
<div className="absolute top-1/2 left-0 w-full -translate-y-1/2 flex justify-between px-2">
|
||||
{PIPELINE_ICONS.map((Icon, i) => (
|
||||
<div
|
||||
@@ -310,8 +322,8 @@ export default function TranslatePage() {
|
||||
className={cn(
|
||||
'w-12 h-12 rounded-2xl border-4 border-white dark:border-[#141414] shadow-xl flex items-center justify-center z-10 transition-all duration-500',
|
||||
submit.progress > (i * 25)
|
||||
? 'bg-brand-dark text-white scale-110 dark:bg-brand-accent'
|
||||
: 'bg-brand-muted text-brand-dark/40 dark:bg-white/10 dark:text-white/40'
|
||||
? 'bg-brand-dark text-white scale-110 dark:bg-brand-accent dark:text-brand-dark font-bold'
|
||||
: 'bg-brand-muted text-brand-dark/25 dark:bg-[#1f1f1f] dark:text-white/20'
|
||||
)}
|
||||
>
|
||||
<Icon size={18} />
|
||||
@@ -324,107 +336,100 @@ export default function TranslatePage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-end mb-8">
|
||||
<span className="text-[10px] font-black text-brand-dark/50 uppercase tracking-[0.3em] dark:text-white/50">
|
||||
{activeStepIdx < 2 ? t('dashboard.translate.steps.uploading') : t('dashboard.translate.steps.starting')}
|
||||
<div className="flex justify-between items-end mt-12 pt-6">
|
||||
<span className="text-[10px] font-bold text-brand-dark/30 dark:text-white/30 uppercase tracking-[0.3em]">
|
||||
{activeStepIdx < 2 ? 'Phase 1: Initialisation' : 'Phase 2: Reconstruction Contextuelle'}
|
||||
</span>
|
||||
<span className="text-7xl font-black text-brand-dark dark:text-white">
|
||||
<span className="text-7xl font-serif font-medium text-brand-dark dark:text-white leading-none">
|
||||
{Math.round(submit.progress)}%
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-6 pt-12 border-t border-black/5 dark:border-white/5">
|
||||
<StatBox icon={<FileText size={18} />} value={`${Math.round(submit.progress)}%`} label={t('dashboard.translate.segments')} />
|
||||
<StatBox icon={<Zap size={18} />} value="99.9%" label={t('dashboard.translate.quality')} />
|
||||
<StatBox icon={<Clock size={18} />} value="Turbo" label={t('dashboard.translate.segPerMin')} />
|
||||
<StatBox icon={<Activity size={18} />} value={formatElapsed(elapsed)} label={t('dashboard.translate.elapsed')} />
|
||||
<div className="grid grid-cols-4 gap-4 pt-12 border-t border-black/5 dark:border-white/5">
|
||||
<StatBox icon={<FileText size={18} />} value={`${Math.round(submit.progress)}%`} label="segments" />
|
||||
<StatBox icon={<Zap size={18} />} value="99.9%" label="précision" />
|
||||
<StatBox icon={<Clock size={18} />} value="Turbo" label="vitesse" />
|
||||
<StatBox icon={<Activity size={18} />} value={formatElapsed(elapsed)} label="temps" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── COMPLETE STATE: Success with download ─────────── */}
|
||||
{showComplete && (
|
||||
<div className="editorial-card p-16 h-full border-none shadow-editorial bg-white dark:bg-[#141414]">
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="p-8 bg-brand-accent/5 border border-brand-accent/10 rounded-[32px] flex items-center justify-between mb-16 shadow-inner dark:bg-brand-accent/10 dark:border-brand-accent/20">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="w-14 h-14 bg-brand-accent rounded-full flex items-center justify-center text-white shadow-xl">
|
||||
<CheckCircle2 size={28} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[13px] font-black uppercase tracking-[0.1em] text-brand-dark dark:text-white">
|
||||
{t('dashboard.translate.completed')}
|
||||
</p>
|
||||
<p className="text-[10px] text-brand-dark/40 font-bold uppercase mt-1 tracking-widest dark:text-white/40">
|
||||
{submit.fileName}
|
||||
</p>
|
||||
</div>
|
||||
<div className="editorial-card p-12 h-full border-none shadow-editorial bg-white dark:bg-[#141414] flex flex-col space-y-12">
|
||||
<div className="p-8 bg-brand-accent/5 border border-brand-accent/10 rounded-[32px] flex items-center justify-between shadow-inner dark:bg-brand-accent/10 dark:border-brand-accent/20">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="w-14 h-14 bg-brand-accent rounded-full flex items-center justify-center text-white shadow-xl">
|
||||
<CheckCircle2 size={28} />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<p className="text-[13px] font-bold uppercase tracking-[0.1em] text-brand-dark dark:text-white">
|
||||
Traduction terminée
|
||||
</p>
|
||||
<p className="text-[10px] text-brand-dark/40 dark:text-white/40 font-bold uppercase mt-1 tracking-widest max-w-[300px] truncate">
|
||||
{submit.fileName}
|
||||
</p>
|
||||
</div>
|
||||
<span className="px-5 py-2 bg-white dark:bg-[#1a1a1a] rounded-full text-[11px] font-black uppercase tracking-widest text-brand-accent border border-brand-accent/20 shadow-sm">
|
||||
{qualityLabel}
|
||||
</span>
|
||||
</div>
|
||||
<span className="px-5 py-2 bg-white dark:bg-[#1a1a1a] rounded-full text-[9px] font-bold uppercase tracking-widest text-brand-accent border border-brand-accent/20 shadow-sm shrink-0">
|
||||
✓ Qualité Maître
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col items-center justify-center py-20 bg-brand-muted/20 rounded-[40px] border border-black/5 dark:bg-white/5 dark:border-white/5">
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
className="premium-button px-24 py-6 text-xl !rounded-full flex items-center gap-6 mb-8 group"
|
||||
>
|
||||
<Download size={28} className="group-hover:translate-y-1 transition-transform" />
|
||||
{t('dashboard.translate.complete.download')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNewTranslation}
|
||||
className="text-[10px] font-black uppercase tracking-[0.3em] text-brand-dark/40 hover:text-brand-dark transition-colors dark:text-white/40 dark:hover:text-white"
|
||||
>
|
||||
+ {t('dashboard.translate.complete.newTranslation')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col items-center justify-center py-16 bg-brand-muted/20 dark:bg-white/5 rounded-[40px] border border-black/5 dark:border-white/5">
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
className="premium-button px-24 py-6 text-xl !rounded-full flex items-center gap-6 mb-8 group cursor-pointer hover:scale-[1.02] active:scale-95"
|
||||
>
|
||||
<Download size={28} className="group-hover:translate-y-1 transition-transform" />
|
||||
Télécharger
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNewTranslation}
|
||||
className="text-[10px] font-bold uppercase tracking-[0.3em] text-brand-dark/30 hover:text-brand-dark dark:text-white/30 dark:hover:text-white transition-colors"
|
||||
>
|
||||
+ Nouvelle traduction
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── FAILED STATE ───────────────────────────────────── */}
|
||||
{showFailed && (
|
||||
<div className="editorial-card p-10 bg-white border-none shadow-editorial dark:bg-[#141414]">
|
||||
{/* Error message — friendly language */}
|
||||
<div className="rounded-[24px] bg-red-50 border-2 border-red-200 dark:bg-red-950/30 dark:border-red-800/40 p-6 mb-6" role="alert">
|
||||
<div className="editorial-card p-10 bg-white dark:bg-[#141414] border-none shadow-editorial space-y-6">
|
||||
<div className="rounded-[24px] bg-red-50 border-2 border-red-200 dark:bg-red-950/20 dark:border-red-900/30 p-6" role="alert">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-10 h-10 rounded-2xl bg-red-100 dark:bg-red-900/40 flex items-center justify-center shrink-0">
|
||||
<div className="w-10 h-10 rounded-2xl bg-red-100 dark:bg-red-900/30 flex items-center justify-center shrink-0">
|
||||
<AlertTriangle className="size-5 text-red-500" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-black uppercase tracking-tight text-red-600 dark:text-red-400 mb-2">{t('dashboard.translate.error.title')}</p>
|
||||
<p className="text-sm text-red-600/80 dark:text-red-300/80 leading-relaxed">{humanFriendlyError(submit.error)}</p>
|
||||
<div className="flex-1 min-w-0 text-left">
|
||||
<p className="text-sm font-bold uppercase tracking-tight text-red-600 dark:text-red-400 mb-2">Erreur lors de la traduction</p>
|
||||
<p className="text-xs text-red-600/80 dark:text-red-300/80 leading-relaxed font-medium">{humanFriendlyError(submit.error)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* File strip */}
|
||||
{(submit.fileName || upload.file?.name) && upload.file && (
|
||||
<div className="mb-6">
|
||||
<FileStrip file={upload.file} onRemove={upload.removeFile} onReplace={() => replaceInputRef.current?.click()} t={t} />
|
||||
<input ref={replaceInputRef} type="file" accept=".xlsx,.docx,.pptx,.pdf" className="hidden" onChange={upload.handleFileSelect} />
|
||||
</div>
|
||||
<FileStrip file={upload.file} onRemove={upload.removeFile} onReplace={() => replaceInputRef.current?.click()} t={t} />
|
||||
)}
|
||||
<input ref={replaceInputRef} type="file" accept=".xlsx,.docx,.pptx,.pdf" className="hidden" onChange={upload.handleFileSelect} />
|
||||
|
||||
{/* Action buttons */}
|
||||
<div className="flex flex-col gap-3">
|
||||
{upload.file && config.isConfigValid && (
|
||||
<button
|
||||
onClick={handleRetry}
|
||||
className="premium-button w-full py-5 text-[12px] uppercase tracking-[0.25em] flex items-center justify-center gap-3 !rounded-2xl"
|
||||
className="premium-button w-full py-5 text-[12px] uppercase tracking-[0.25em] flex items-center justify-center gap-3 !rounded-2xl cursor-pointer hover:scale-[1.01] active:scale-98"
|
||||
>
|
||||
<RotateCcw size={18} />
|
||||
{t('dashboard.translate.retry')}
|
||||
Réessayer
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={handleNewTranslation}
|
||||
className="w-full py-4 border-2 border-black/10 dark:border-white/10 rounded-2xl text-[11px] font-black uppercase tracking-[0.25em] text-brand-dark/50 dark:text-white/50 hover:text-brand-dark dark:hover:text-white hover:border-brand-dark/20 dark:hover:border-white/20 transition-all flex items-center justify-center gap-3"
|
||||
className="w-full py-4 border border-black/10 dark:border-white/10 rounded-2xl text-[10px] font-bold uppercase tracking-[0.25em] text-brand-dark/50 dark:text-white/50 hover:text-brand-dark dark:hover:text-white transition-all flex items-center justify-center gap-3 cursor-pointer hover:bg-brand-muted/30 dark:hover:bg-white/5"
|
||||
>
|
||||
<Upload size={16} />
|
||||
{t('dashboard.translate.newFile')}
|
||||
Téléverser un autre fichier
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -432,20 +437,20 @@ export default function TranslatePage() {
|
||||
</div>
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════ */}
|
||||
{/* RIGHT (4 cols) — Config / Monitor / Summary */}
|
||||
{/* RIGHT (5 cols) — Config / Monitor / Summary */}
|
||||
{/* ═══════════════════════════════════════════════════════ */}
|
||||
<div className="lg:col-span-4">
|
||||
<div className="lg:col-span-5 space-y-6">
|
||||
|
||||
{/* ── CONFIG (upload / configuring / failed) ──────────── */}
|
||||
{(showUpload || showConfiguring || showFailed) && (
|
||||
<div className="editorial-card bg-white border-none shadow-editorial dark:bg-[#141414] overflow-hidden flex flex-col lg:sticky lg:top-8 lg:max-h-[calc(100vh-6rem)]">
|
||||
<div className="editorial-card bg-white dark:bg-[#141414] border-none shadow-editorial overflow-hidden flex flex-col lg:sticky lg:top-8 lg:max-h-[calc(100vh-6rem)]">
|
||||
{/* Scrollable config content */}
|
||||
<div className="flex-1 overflow-y-auto p-10 pb-6">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-[0.3em] mb-10 text-brand-dark/50 border-b border-black/5 pb-6 dark:text-white/50 dark:border-white/5">
|
||||
{t('landing.translate.configuration')}
|
||||
<div className="flex-1 overflow-y-auto p-6 space-y-5">
|
||||
<h4 className="text-[10px] font-bold uppercase tracking-[0.2em] text-brand-dark/30 dark:text-white/30 pb-3 border-b border-black/[0.03] dark:border-white/[0.03]">
|
||||
{t('landing.translate.configuration') || 'Configuration'}
|
||||
</h4>
|
||||
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-6">
|
||||
<LanguageSelector
|
||||
sourceLang={config.sourceLang} targetLang={config.targetLang}
|
||||
languages={config.languages} isLoading={config.isLoadingLanguages}
|
||||
@@ -463,7 +468,7 @@ export default function TranslatePage() {
|
||||
{config.provider && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn(
|
||||
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[10px] font-black uppercase tracking-[0.15em]",
|
||||
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[9px] font-bold uppercase tracking-[0.1em]",
|
||||
config.mode === 'llm'
|
||||
? "bg-brand-accent/10 text-brand-accent border border-brand-accent/20"
|
||||
: "bg-brand-muted/50 text-brand-dark/40 dark:bg-white/5 dark:text-white/40 border border-transparent"
|
||||
@@ -475,7 +480,7 @@ export default function TranslatePage() {
|
||||
)}
|
||||
</span>
|
||||
{config.mode === 'classic' && config.isPro && (
|
||||
<span className="text-[11px] text-brand-dark/40 dark:text-white/40 italic">
|
||||
<span className="text-[9px] text-brand-dark/40 dark:text-white/40 italic font-medium leading-none">
|
||||
{t('dashboard.translate.glossaryLLMHint')}
|
||||
</span>
|
||||
)}
|
||||
@@ -494,47 +499,70 @@ export default function TranslatePage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Translate Images — Office files and LLM mode only */}
|
||||
{!isPdf && config.mode === 'llm' && (
|
||||
<div className="flex items-start justify-between rounded-2xl border border-black/5 dark:border-white/5 bg-brand-muted/30 dark:bg-white/5 p-4">
|
||||
<div className="flex gap-3 min-w-0 flex-1">
|
||||
<ImageIcon className="size-4 shrink-0 text-brand-accent mt-0.5" />
|
||||
<div className="flex flex-col text-left">
|
||||
<span className="text-[10px] font-bold uppercase tracking-tight text-brand-dark dark:text-white">
|
||||
{t('dashboard.translate.translateImages') || 'Traduire les images'}
|
||||
</span>
|
||||
<span className="text-[8px] text-brand-dark/40 dark:text-white/40 font-bold uppercase tracking-widest mt-1.5 leading-normal">
|
||||
{t('dashboard.translate.translateImagesDesc') || 'Traduire les textes incrustés'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={config.translateImages}
|
||||
onCheckedChange={config.setTranslateImages}
|
||||
disabled={submit.isSubmitting}
|
||||
aria-label={t('dashboard.translate.translateImages')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* PDF mode selector */}
|
||||
{isPdf && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-[11px] font-black text-brand-dark/50 uppercase tracking-[0.15em] block mb-3 dark:text-white/50">
|
||||
{t('dashboard.translate.pdfMode.title')}
|
||||
<div className="space-y-2 text-left">
|
||||
<label className="text-[9px] font-bold text-brand-dark/40 dark:text-white/40 uppercase tracking-[0.15em] block mb-2">
|
||||
{t('dashboard.translate.pdfMode.title') || 'Mode PDF'}
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPdfMode('layout')}
|
||||
className={cn(
|
||||
'flex flex-col items-start rounded-2xl border-2 p-3 text-start transition-all',
|
||||
'flex flex-col items-start rounded-2xl border p-3.5 text-start transition-all',
|
||||
pdfMode === 'layout'
|
||||
? 'border-brand-accent bg-brand-accent/5'
|
||||
: 'border-black/5 bg-brand-muted/30 hover:border-brand-accent/20 dark:border-white/10 dark:bg-white/5'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-[11px] font-black uppercase tracking-tight text-brand-dark dark:text-white">
|
||||
<FileText className="size-4 text-brand-accent" />
|
||||
{t('dashboard.translate.pdfMode.preserveLayout')}
|
||||
<div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-tight text-brand-dark dark:text-white">
|
||||
<FileText className="size-3.5 text-brand-accent" />
|
||||
{t('dashboard.translate.pdfMode.preserveLayout') || 'Mise en page'}
|
||||
</div>
|
||||
<p className="mt-1 text-[11px] text-brand-dark/55 font-bold uppercase tracking-widest leading-relaxed dark:text-white/50">
|
||||
{t('dashboard.translate.pdfMode.preserveLayoutDesc')}
|
||||
<p className="mt-1.5 text-[8px] text-brand-dark/40 dark:text-white/40 font-bold uppercase tracking-widest leading-relaxed">
|
||||
Conserver la mise en page
|
||||
</p>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPdfMode('text_only')}
|
||||
className={cn(
|
||||
'flex flex-col items-start rounded-2xl border-2 p-3 text-start transition-all',
|
||||
'flex flex-col items-start rounded-2xl border p-3.5 text-start transition-all',
|
||||
pdfMode === 'text_only'
|
||||
? 'border-brand-accent bg-brand-accent/5'
|
||||
: 'border-black/5 bg-brand-muted/30 hover:border-brand-accent/20 dark:border-white/10 dark:bg-white/5'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-[11px] font-black uppercase tracking-tight text-brand-dark dark:text-white">
|
||||
<Languages className="size-4 text-brand-accent" />
|
||||
{t('dashboard.translate.pdfMode.textOnly')}
|
||||
<div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-tight text-brand-dark dark:text-white">
|
||||
<Languages className="size-3.5 text-brand-accent" />
|
||||
Texte brut
|
||||
</div>
|
||||
<p className="mt-1 text-[11px] text-brand-dark/55 font-bold uppercase tracking-widest leading-relaxed dark:text-white/50">
|
||||
{t('dashboard.translate.pdfMode.textOnlyDesc')}
|
||||
<p className="mt-1.5 text-[8px] text-brand-dark/40 dark:text-white/40 font-bold uppercase tracking-widest leading-relaxed">
|
||||
Traduction rapide du texte uniquement
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
@@ -549,32 +577,32 @@ export default function TranslatePage() {
|
||||
disabled={!config.isConfigValid || submit.isSubmitting || !upload.file}
|
||||
onClick={handleTranslate}
|
||||
className={cn(
|
||||
'w-full py-5 rounded-2xl text-[13px] font-black uppercase tracking-[0.2em] flex items-center justify-center gap-3 transition-all duration-200',
|
||||
'w-full py-4 text-[10px] font-bold uppercase tracking-[0.2em] flex items-center justify-center gap-2 rounded-2xl transition-all shadow-sm active:scale-98',
|
||||
config.isConfigValid && upload.file && !submit.isSubmitting
|
||||
? 'bg-brand-dark text-white hover:bg-brand-dark/90 shadow-lg shadow-brand-dark/20 dark:bg-brand-accent dark:text-brand-dark dark:hover:bg-brand-accent/90'
|
||||
: 'bg-black/5 dark:bg-white/5 text-brand-dark/45 dark:text-white/45 cursor-not-allowed'
|
||||
? 'bg-brand-dark text-white hover:bg-brand-accent dark:bg-brand-accent dark:text-brand-dark hover:shadow-xl cursor-pointer'
|
||||
: 'bg-brand-muted/70 text-brand-dark/25 dark:bg-white/5 dark:text-white/20 cursor-not-allowed border border-black/[0.03] dark:border-white/[0.03]'
|
||||
)}
|
||||
>
|
||||
{submit.isSubmitting ? (
|
||||
<><Loader2 className="size-5 animate-spin" />{t('dashboard.translate.submitting')}</>
|
||||
<><Loader2 className="size-4 animate-spin" /> Soumission...</>
|
||||
) : (
|
||||
<><ArrowRight size={20} />{t('dashboard.translate.submit')}</>
|
||||
<>Lancer la traduction <ArrowRight size={13} className={upload.file ? 'text-brand-accent' : 'opacity-20'} /></>
|
||||
)}
|
||||
</button>
|
||||
{!upload.file && (
|
||||
<p className="text-center text-[11px] text-brand-dark/40 dark:text-white/40 mt-2 font-bold uppercase tracking-widest">↑ {t('dashboard.translate.noFile')}</p>
|
||||
<p className="text-center text-[8px] text-brand-dark/30 dark:text-white/30 mt-2.5 font-bold uppercase tracking-widest">↑ Veuillez charger un fichier pour commencer</p>
|
||||
)}
|
||||
{upload.file && !config.targetLang && (
|
||||
<p className="text-center text-[11px] text-brand-dark/40 dark:text-white/40 mt-2 font-bold uppercase tracking-widest">↑ {t('dashboard.translate.noTargetLang')}</p>
|
||||
<p className="text-center text-[8px] text-brand-dark/30 dark:text-white/30 mt-2.5 font-bold uppercase tracking-widest">↑ Veuillez choisir une langue cible</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="shrink-0 flex justify-between px-6 pb-5 pt-1 text-[10px] font-black uppercase tracking-[0.15em] text-brand-dark/40 dark:text-white/40">
|
||||
<span className="flex items-center gap-2">
|
||||
<ShieldCheck size={12} /> {t('landing.translate.zeroRetention')}
|
||||
<div className="shrink-0 flex justify-between px-6 pb-5 pt-1 text-[7.5px] font-bold uppercase tracking-[0.15em] text-brand-dark/30 dark:text-white/30">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<ShieldCheck size={12} /> {t('landing.translate.zeroRetention') || 'Rétention Zéro'}
|
||||
</span>
|
||||
<span className="flex items-center gap-2">
|
||||
<Clock size={12} /> {t('landing.translate.filesDeleted')}
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Clock size={12} /> {t('landing.translate.filesDeleted') || 'Fichiers supprimés post-traitement'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -582,28 +610,28 @@ export default function TranslatePage() {
|
||||
|
||||
{/* ── MONITOR (processing) ────────────────────────────── */}
|
||||
{showProcessing && (
|
||||
<div className="editorial-card p-10 bg-white border-none shadow-editorial h-full dark:bg-[#141414]">
|
||||
<h4 className="text-[11px] font-black uppercase tracking-[0.3em] mb-12 flex items-center gap-3 text-brand-dark/45 dark:text-white/45">
|
||||
<div className="editorial-card p-6 bg-white dark:bg-[#141414] border-none shadow-editorial h-full">
|
||||
<h4 className="text-[10px] font-bold uppercase tracking-[0.2em] mb-8 flex items-center gap-3 text-brand-dark/45 dark:text-white/45 pb-3 border-b border-black/[0.03] dark:border-white/[0.03]">
|
||||
<div className="w-2 h-2 bg-brand-accent rounded-full animate-ping" />
|
||||
{t('dashboard.translate.liveMonitor')}
|
||||
Moniteur IA
|
||||
</h4>
|
||||
|
||||
{/* File summary */}
|
||||
{(submit.fileName || upload.file?.name) && (
|
||||
<div className="p-6 bg-brand-muted rounded-[32px] mb-10 flex items-center gap-5 border border-black/5 dark:bg-white/5 dark:border-white/5">
|
||||
<div className="w-12 h-12 bg-white rounded-2xl flex items-center justify-center text-brand-accent shadow-sm dark:bg-[#1a1a1a]">
|
||||
<div className="p-4 bg-brand-muted dark:bg-white/5 rounded-2xl mb-8 flex items-center gap-4 border border-black/5 dark:border-white/5">
|
||||
<div className="w-10 h-10 bg-white dark:bg-[#1a1a1a] rounded-xl flex items-center justify-center text-brand-accent shadow-sm">
|
||||
{(() => {
|
||||
const name = submit.fileName || upload.file?.name || '';
|
||||
const ext = name.split('.').pop()?.toLowerCase() ?? '';
|
||||
const FileIcon = FILE_ICONS[ext] ?? FileText;
|
||||
return <FileIcon size={24} />;
|
||||
return <FileIcon size={20} className={FILE_COLORS[ext]} />;
|
||||
})()}
|
||||
</div>
|
||||
<div className="overflow-hidden">
|
||||
<p className="text-[11px] font-black uppercase tracking-tight truncate text-brand-dark dark:text-white">
|
||||
<div className="overflow-hidden text-left">
|
||||
<p className="text-[11px] font-bold truncate text-brand-dark dark:text-white">
|
||||
{submit.fileName || upload.file?.name}
|
||||
</p>
|
||||
<p className="text-[11px] text-brand-dark/45 font-bold uppercase tracking-widest mt-1 dark:text-white/45">
|
||||
<p className="text-[9px] text-brand-dark/45 dark:text-white/45 font-bold uppercase tracking-widest mt-1">
|
||||
{upload.file ? `${fmt(upload.file.size)} ` : ''}{(submit.fileName || upload.file?.name || '').split('.').pop()?.toUpperCase()}
|
||||
</p>
|
||||
</div>
|
||||
@@ -611,30 +639,30 @@ export default function TranslatePage() {
|
||||
)}
|
||||
|
||||
{/* Config summary */}
|
||||
<div className="space-y-8 mb-16 px-2">
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/50 dark:text-white/50">
|
||||
<span>{t('dashboard.translate.language.source')}</span>
|
||||
<div className="space-y-6 mb-8 px-2 text-left">
|
||||
<div className="flex justify-between items-center text-[9px] font-bold uppercase tracking-[0.2em] text-brand-dark/40 dark:text-white/40">
|
||||
<span>Source</span>
|
||||
<span className="text-brand-dark dark:text-white">{srcLangName.toUpperCase()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/50 dark:text-white/50">
|
||||
<span>{t('dashboard.translate.language.target')}</span>
|
||||
<div className="flex justify-between items-center text-[9px] font-bold uppercase tracking-[0.2em] text-brand-dark/40 dark:text-white/40">
|
||||
<span>Cible</span>
|
||||
<span className="text-brand-accent">{tgtLangName.toUpperCase()}</span>
|
||||
</div>
|
||||
{currentProvider && (
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/50 dark:text-white/50">
|
||||
<span>{t('dashboard.translate.engine')}</span>
|
||||
<div className="flex justify-between items-center text-[9px] font-bold uppercase tracking-[0.2em] text-brand-dark/40 dark:text-white/40">
|
||||
<span>Moteur</span>
|
||||
<span className="text-brand-dark dark:text-white">{currentProvider.label.toUpperCase()}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Quality progress */}
|
||||
<div className="pt-10 border-t border-black/10 dark:border-white/10">
|
||||
<div className="flex justify-between text-[10px] font-black uppercase tracking-[0.4em] mb-4">
|
||||
<span className="text-brand-dark/50 dark:text-white/50">{t('dashboard.translate.quality')}</span>
|
||||
<span className="text-brand-accent">{qualityLabel}</span>
|
||||
<div className="pt-6 border-t border-black/5 dark:border-white/5 text-left">
|
||||
<div className="flex justify-between text-[9px] font-bold uppercase tracking-[0.2em] mb-3">
|
||||
<span className="text-brand-dark/40 dark:text-white/40">Intégrité Layout</span>
|
||||
<span className="text-brand-accent">100% SECURE</span>
|
||||
</div>
|
||||
<div className="h-2 bg-brand-muted rounded-full overflow-hidden p-0.5 dark:bg-white/10">
|
||||
<div className="h-2 bg-brand-muted dark:bg-white/5 rounded-full overflow-hidden p-0.5">
|
||||
<div
|
||||
className="h-full bg-brand-accent rounded-full transition-all duration-700"
|
||||
style={{ width: `${Math.min(95, 40 + submit.progress * 0.55)}%` }}
|
||||
@@ -644,45 +672,45 @@ export default function TranslatePage() {
|
||||
|
||||
<button
|
||||
onClick={handleNewTranslation}
|
||||
className="w-full mt-16 py-5 border border-red-50 text-red-500 rounded-2xl text-[10px] font-black uppercase tracking-[0.3em] flex items-center justify-center gap-3 hover:bg-red-50 transition-all dark:border-red-900/30 dark:text-red-400 dark:hover:bg-red-950/30"
|
||||
className="w-full mt-12 py-4 border border-red-100 text-red-500 rounded-2xl text-[9px] font-bold uppercase tracking-[0.2em] flex items-center justify-center gap-2 hover:bg-red-50 dark:border-red-950/20 dark:hover:bg-red-950/30 transition-all cursor-pointer"
|
||||
>
|
||||
<RotateCcw size={16} /> {t('dashboard.translate.cancel')}
|
||||
⟳ Annuler le processus
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── SUMMARY (complete) ──────────────────────────────── */}
|
||||
{showComplete && (
|
||||
<div className="editorial-card p-10 bg-white border-none shadow-editorial h-full dark:bg-[#141414]">
|
||||
<h4 className="text-[11px] font-black uppercase tracking-[0.3em] mb-12 flex items-center gap-3 text-brand-dark/45 dark:text-white/45">
|
||||
<div className="editorial-card p-6 bg-white dark:bg-[#141414] border-none shadow-editorial h-full">
|
||||
<h4 className="text-[10px] font-bold uppercase tracking-[0.2em] mb-8 flex items-center gap-3 text-brand-dark/45 dark:text-white/45 pb-3 border-b border-black/[0.03] dark:border-white/[0.03]">
|
||||
<CheckCircle2 size={14} className="text-emerald-500" />
|
||||
{t('dashboard.translate.summary')}
|
||||
Récapitulatif
|
||||
</h4>
|
||||
|
||||
<div className="space-y-8 mb-16 px-2">
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/50 dark:text-white/50">
|
||||
<span>{t('dashboard.translate.language.source')}</span>
|
||||
<div className="space-y-6 mb-8 px-2 text-left">
|
||||
<div className="flex justify-between items-center text-[9px] font-bold uppercase tracking-[0.2em] text-brand-dark/40 dark:text-white/40">
|
||||
<span>Source</span>
|
||||
<span className="text-brand-dark dark:text-white">{srcLangName.toUpperCase()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/50 dark:text-white/50">
|
||||
<span>{t('dashboard.translate.language.target')}</span>
|
||||
<div className="flex justify-between items-center text-[9px] font-bold uppercase tracking-[0.2em] text-brand-dark/40 dark:text-white/40">
|
||||
<span>Cible</span>
|
||||
<span className="text-brand-accent">{tgtLangName.toUpperCase()}</span>
|
||||
</div>
|
||||
{currentProvider && (
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/50 dark:text-white/50">
|
||||
<span>{t('dashboard.translate.engine')}</span>
|
||||
<div className="flex justify-between items-center text-[9px] font-bold uppercase tracking-[0.2em] text-brand-dark/40 dark:text-white/40">
|
||||
<span>Moteur</span>
|
||||
<span className="text-brand-dark dark:text-white">{currentProvider.label.toUpperCase()}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="pt-10 border-t border-black/10 dark:border-white/10">
|
||||
<div className="flex justify-between text-[10px] font-black uppercase tracking-[0.4em] mb-4">
|
||||
<span className="text-brand-dark/50 dark:text-white/50">{t('dashboard.translate.quality')}</span>
|
||||
<span className="text-brand-accent">{qualityLabel}</span>
|
||||
<div className="pt-6 border-t border-black/5 dark:border-white/5 text-left">
|
||||
<div className="flex justify-between text-[9px] font-bold uppercase tracking-[0.2em] mb-3">
|
||||
<span className="text-brand-dark/40 dark:text-white/40">Intégrité Layout</span>
|
||||
<span className="text-brand-accent">100% OK</span>
|
||||
</div>
|
||||
<div className="h-2 bg-brand-muted rounded-full overflow-hidden p-0.5 dark:bg-white/10">
|
||||
<div className="h-full bg-brand-accent rounded-full" style={{ width: '95%' }} />
|
||||
<div className="h-2 bg-brand-muted dark:bg-white/5 rounded-full overflow-hidden p-0.5">
|
||||
<div className="h-full bg-brand-accent rounded-full" style={{ width: '100%' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -692,28 +720,28 @@ export default function TranslatePage() {
|
||||
|
||||
{/* ── MOMENTO PROMO BANNER ──────────────────────────────── */}
|
||||
{(showUpload || showConfiguring || showFailed) && (
|
||||
<div className="mt-20 editorial-card p-12 bg-white border-none shadow-editorial flex flex-col md:flex-row items-center gap-10 group overflow-hidden relative dark:bg-[#141414]">
|
||||
<div className="absolute -right-20 -top-20 w-64 h-64 bg-brand-accent/5 rounded-full blur-3xl group-hover:bg-brand-accent/10 transition-colors" />
|
||||
<div className="mt-12 editorial-card p-10 bg-white dark:bg-[#141414] border-none shadow-editorial flex flex-col md:flex-row items-center gap-8 group overflow-hidden relative">
|
||||
<div className="absolute -right-20 -top-20 w-64 h-64 bg-brand-accent/5 rounded-full blur-3xl group-hover:bg-brand-accent/10 transition-colors pointer-events-none" />
|
||||
|
||||
<div className="w-24 h-24 bg-brand-dark rounded-[32px] flex items-center justify-center text-white text-5xl font-black shadow-2xl shrink-0 group-hover:rotate-12 transition-transform duration-500 dark:bg-brand-accent dark:text-brand-dark">
|
||||
<div className="w-16 h-16 bg-brand-dark dark:bg-brand-accent rounded-[24px] flex items-center justify-center text-white dark:text-brand-dark text-3xl font-black shadow-2xl shrink-0 group-hover:rotate-6 transition-transform duration-500">
|
||||
M
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<span className="accent-pill !px-3 !py-1 text-[11px] italic">{t('memento.title')}</span>
|
||||
<h3 className="text-2xl font-black uppercase tracking-tight text-brand-dark dark:text-white">{t('memento.title')}</h3>
|
||||
<div className="flex-1 text-left">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<span className="accent-pill !px-2.5 !py-0.5 text-[8px] italic">Ecosystème Wordly</span>
|
||||
<h3 className="text-xl font-bold tracking-tight text-brand-dark dark:text-white uppercase">{t('memento.title')}</h3>
|
||||
</div>
|
||||
<p className="text-sm text-brand-dark/40 font-medium leading-relaxed max-w-2xl dark:text-white/40">
|
||||
<p className="text-xs text-brand-dark/40 dark:text-white/40 font-light leading-relaxed max-w-2xl">
|
||||
{t('memento.slogan')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 shrink-0 w-full md:w-auto">
|
||||
<button className="premium-button px-10 py-4 text-[11px] uppercase tracking-widest !rounded-2xl">
|
||||
<div className="flex flex-col sm:flex-row gap-3 shrink-0 w-full md:w-auto">
|
||||
<button className="premium-button px-8 py-3.5 text-[9px] uppercase tracking-widest !rounded-xl text-center">
|
||||
{t('memento.ctaFree')}
|
||||
</button>
|
||||
<button className="px-10 py-4 border border-black/5 bg-brand-muted text-brand-dark/40 rounded-2xl text-[10px] font-black uppercase tracking-widest hover:text-brand-dark transition-all dark:border-white/10 dark:bg-white/5 dark:text-white/40 dark:hover:text-white">
|
||||
<button className="px-8 py-3.5 border border-black/5 bg-brand-muted text-brand-dark/40 rounded-xl text-[9px] font-bold uppercase tracking-widest hover:text-brand-dark dark:border-white/5 dark:bg-white/5 dark:text-white/40 dark:hover:text-white hover:bg-brand-muted/70 transition-all text-center">
|
||||
{t('memento.ctaMore')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user