diff --git a/database/models.py b/database/models.py
index 015b769..ce43787 100644
--- a/database/models.py
+++ b/database/models.py
@@ -165,7 +165,7 @@ class Translation(Base):
# Translation details
source_language = Column(String(10), default="auto")
target_language = Column(String(10), nullable=False)
- provider = Column(String(50), nullable=False) # google, deepl, ollama, etc.
+ provider = Column(String(50), nullable=False) # google, ollama, etc.
# Processing info
status = Column(
diff --git a/frontend/src/components/landing/hero-visual.tsx b/frontend/src/components/landing/hero-visual.tsx
index cfdc555..f8f2515 100644
--- a/frontend/src/components/landing/hero-visual.tsx
+++ b/frontend/src/components/landing/hero-visual.tsx
@@ -1,187 +1,986 @@
'use client';
-import { useEffect, useState } from 'react';
-import { motion, useReducedMotion, AnimatePresence } from 'framer-motion';
-import { Languages } from 'lucide-react';
+import { useEffect, useState, useCallback } from 'react';
+import { useReducedMotion } from 'framer-motion';
+import {
+ FileText,
+ FileSpreadsheet,
+ Presentation,
+ FileCheck,
+ Sparkles,
+ ArrowRight,
+ Clock,
+ Users,
+ Flame,
+ Check,
+ ShieldCheck,
+ Save,
+ Undo,
+ Redo,
+ Search,
+ Minus,
+ Square,
+ X,
+ Bold,
+ Italic,
+ Underline,
+ AlignLeft,
+ AlignCenter,
+ List,
+ ListOrdered,
+ Table as TableIcon,
+ ChevronDown,
+ ZoomIn,
+ Printer,
+ Maximize2,
+} from 'lucide-react';
import { cn } from '@/lib/utils';
/**
- * Landing hero visual — the product promise drawn instead of photographed:
- * one document whose layout never moves while its language changes.
- * Pure geometry (bars, table, chart) + one real translated heading per card.
+ * Wordly.art — Hero Visual Authentique Office & PDF
+ *
+ * Cette animation recrée fidèlement l'environnement réel d'un logiciel bureautique
+ * (Microsoft Word, Excel, PowerPoint et Adobe PDF) avec :
+ * - Le ruban, la barre de titre, la règle et la barre d'état officielles.
+ * - Une vraie page de document A4 flottante avec du vrai texte lisible (≥ 11px).
+ * - Une onde dorée de numérisation/traduction en temps réel (FR → EN → ES).
+ * - Une stabilité géométrique absolue : zéro décalage, zéro saut de mise en page.
*/
-const SOURCE_HEADING = 'Rapport annuel';
+type OfficeFormat = 'docx' | 'xlsx' | 'pptx' | 'pdf';
-const TARGETS = [
- { code: 'EN', heading: 'Annual report', widths: [0.94, 0.82, 0.88, 0.58, 0.76] },
- { code: 'ES', heading: 'Informe anual', widths: [0.9, 0.86, 0.84, 0.64, 0.8] },
- { code: 'DE', heading: 'Jahresbericht', widths: [0.98, 0.78, 0.92, 0.52, 0.7] },
-] as const;
-
-const SOURCE_WIDTHS = [0.92, 0.8, 0.86, 0.62, 0.78];
-
-const CYCLE_MS = 3600;
-
-function TextLines({ widths, accent = false }: { widths: readonly number[]; accent?: boolean }) {
- return (
-
- {widths.map((w, i) => (
-
- ))}
-
- );
+interface RecipeData {
+ lang: 'FR' | 'EN' | 'ES';
+ langName: string;
+ langCodeDoc: string;
+ docTitle: string;
+ docSubtitle: string;
+ category: string;
+ timeLabel: string;
+ servingsLabel: string;
+ heatLabel: string;
+ ingredientsHeading: string;
+ ingredients: Array<{ amount: string; name: string }>;
+ tableTitle: string;
+ tableHeaders: [string, string, string];
+ tableValues: [string, string, string];
+ stepsHeading: string;
+ steps: Array<{ num: string; title: string; desc: string }>;
+ excelFormula: string;
+ excelHeaders: [string, string, string, string];
+ excelRows: Array<[string, string, string, string]>;
+ excelTotalLabel: string;
+ excelTotalVal: string;
+ pptSlideTitle: string;
+ pptCard1Title: string;
+ pptCard1Text: string;
+ pptCard2Title: string;
+ pptCard2Text: string;
+ guaranteeText: string;
}
-function MiniTable() {
+const RECIPES: RecipeData[] = [
+ {
+ lang: 'FR',
+ langName: 'Français (France)',
+ langCodeDoc: 'FR-FR',
+ docTitle: 'Tarte fine aux pommes & cannelle',
+ docSubtitle: 'Recette traditionnelle • Pâtisserie maison de saison',
+ category: 'Pâtisserie artisanale • Recette n°04',
+ timeLabel: '15 min de préparation',
+ servingsLabel: '4 à 6 personnes',
+ heatLabel: 'Four chaud 180°C',
+ ingredientsHeading: 'Ingrédients nécessaires',
+ ingredients: [
+ { amount: '1 rouleau', name: 'Pâte feuilletée pur beurre dorée' },
+ { amount: '4 pièces', name: 'Pommes Golden finement tranchées' },
+ { amount: '25 g', name: 'Beurre doux fondu tiédi' },
+ { amount: '2 sachets', name: 'Sucre vanillé naturel des îles' },
+ ],
+ tableTitle: 'Paramètres de cuisson au four',
+ tableHeaders: ['Position four', 'Préparation', 'Cuisson dorée'],
+ tableValues: ['Mi-hauteur 180°C', '15 minutes', '25 minutes'],
+ stepsHeading: 'Instructions de préparation',
+ steps: [
+ {
+ num: '01',
+ title: 'Préparer la pâte',
+ desc: 'Dérouler la pâte sur la plaque et piquer le fond à la fourchette.',
+ },
+ {
+ num: '02',
+ title: 'Dresser les pommes',
+ desc: 'Disposer les lamelles de pommes en rosace harmonieuse et serrée.',
+ },
+ {
+ num: '03',
+ title: 'Enfourner et dorer',
+ desc: 'Badigeonner de beurre fondu puis saupoudrer de sucre vanillé.',
+ },
+ ],
+ excelFormula: '=SOMME(C3:C6) & " g de garniture"',
+ excelHeaders: ['Ingrédient', 'Quantité', 'Unité', 'Remarque'],
+ excelRows: [
+ ['Pâte feuilletée', '1', 'rouleau', 'Pur beurre'],
+ ['Pommes Golden', '4', 'pièces', 'Tranches fines'],
+ ['Beurre doux', '25', 'grammes', 'Fondu tiède'],
+ ['Sucre vanillé', '2', 'sachets', 'Arôme naturel'],
+ ],
+ excelTotalLabel: 'Temps de cuisson totale',
+ excelTotalVal: '25 min à 180°C',
+ pptSlideTitle: 'Recette gourmande : Tarte fine aux pommes',
+ pptCard1Title: 'Ingrédients clés',
+ pptCard1Text: 'Pâte feuilletée pur beurre, 4 pommes Golden, beurre doux fondu, sucre vanillé naturel.',
+ pptCard2Title: 'Cuisson & finition',
+ pptCard2Text: 'Disposition en rosace serrée, cuisson 25 min à 180°C jusqu’à dorure parfaite.',
+ guaranteeText: 'Mise en page originale 100% préservée',
+ },
+ {
+ lang: 'EN',
+ langName: 'English (United States)',
+ langCodeDoc: 'EN-US',
+ docTitle: 'Crisp apple & cinnamon tart',
+ docSubtitle: 'Traditional recipe • Seasonal homemade pastry',
+ category: 'Artisanal pastry • Recipe #04',
+ timeLabel: '15 min preparation time',
+ servingsLabel: '4 to 6 servings',
+ heatLabel: 'Hot oven at 180°C',
+ ingredientsHeading: 'Required ingredients',
+ ingredients: [
+ { amount: '1 sheet', name: 'Golden all-butter puff pastry sheet' },
+ { amount: '4 units', name: 'Fresh Golden apples thinly sliced' },
+ { amount: '25 g', name: 'Melted unsalted sweet butter' },
+ { amount: '2 packs', name: 'Natural island vanilla sugar dust' },
+ ],
+ tableTitle: 'Oven baking specifications',
+ tableHeaders: ['Oven position', 'Preparation', 'Golden baking'],
+ tableValues: ['Middle shelf 180°C', '15 minutes', '25 minutes'],
+ stepsHeading: 'Preparation instructions',
+ steps: [
+ {
+ num: '01',
+ title: 'Prepare the crust',
+ desc: 'Unroll pastry on the tray and prick the base evenly with a fork.',
+ },
+ {
+ num: '02',
+ title: 'Arrange the fruit',
+ desc: 'Place the apple slices in an elegant, tight spiral pattern.',
+ },
+ {
+ num: '03',
+ title: 'Bake to golden',
+ desc: 'Brush with melted butter and dust evenly with vanilla sugar.',
+ },
+ ],
+ excelFormula: '=SUM(C3:C6) & " g filling total"',
+ excelHeaders: ['Ingredient', 'Quantity', 'Unit', 'Note'],
+ excelRows: [
+ ['Puff pastry', '1', 'sheet', 'All-butter'],
+ ['Golden apples', '4', 'units', 'Thin slices'],
+ ['Unsalted butter', '25', 'grams', 'Melted warm'],
+ ['Vanilla sugar', '2', 'packs', 'Natural flavor'],
+ ],
+ excelTotalLabel: 'Total baking duration',
+ excelTotalVal: '25 min at 180°C',
+ pptSlideTitle: 'Gourmet Recipe: Crisp Apple Tart',
+ pptCard1Title: 'Key Ingredients',
+ pptCard1Text: 'All-butter puff pastry, 4 Golden apples, melted unsalted butter, natural vanilla sugar.',
+ pptCard2Title: 'Baking & Finish',
+ pptCard2Text: 'Arrange in tight spiral, bake 25 min at 180°C until crisp and golden brown.',
+ guaranteeText: 'Original layout 100% preserved',
+ },
+ {
+ lang: 'ES',
+ langName: 'Español (España)',
+ langCodeDoc: 'ES-ES',
+ docTitle: 'Tarta fina de manzana & canela',
+ docSubtitle: 'Receta tradicional • Repostería casera de otoño',
+ category: 'Repostería artesanal • Receta n°04',
+ timeLabel: '15 min de preparación',
+ servingsLabel: '4 a 6 personas',
+ heatLabel: 'Horno caliente 180°C',
+ ingredientsHeading: 'Ingredientes necesarios',
+ ingredients: [
+ { amount: '1 lámina', name: 'Masa de hojaldre dorado con mantequilla' },
+ { amount: '4 piezas', name: 'Manzanas Golden finamente laminadas' },
+ { amount: '25 g', name: 'Mantequilla dulce derretida templada' },
+ { amount: '2 sobres', name: 'Azúcar avainillado natural fino' },
+ ],
+ tableTitle: 'Parámetros de horneado al horno',
+ tableHeaders: ['Posición horno', 'Preparación', 'Horneado dorado'],
+ tableValues: ['Altura media 180°C', '15 minutos', '25 minutes'],
+ stepsHeading: 'Instrucciones de preparación',
+ steps: [
+ {
+ num: '01',
+ title: 'Preparar la base',
+ desc: 'Extender la masa sobre la bandeja y pinchar la base con tenedor.',
+ },
+ {
+ num: '02',
+ title: 'Colocar la fruta',
+ desc: 'Disponer las láminas de manzana en una espiral bien apretada.',
+ },
+ {
+ num: '03',
+ title: 'Hornear y dorar',
+ desc: 'Pincelar con mantequilla y espolvorear el azúcar avainillado.',
+ },
+ ],
+ excelFormula: '=SUMA(C3:C6) & " g de relleno"',
+ excelHeaders: ['Ingrediente', 'Cantidad', 'Unidad', 'Detalle'],
+ excelRows: [
+ ['Masa hojaldre', '1', 'lámina', 'Con mantequilla'],
+ ['Manzanas Golden', '4', 'piezas', 'Láminas finas'],
+ ['Mantequilla dulce', '25', 'gramos', 'Derretida tibia'],
+ ['Azúcar vainillado', '2', 'sobres', 'Aroma natural'],
+ ],
+ excelTotalLabel: 'Tiempo de cocción total',
+ excelTotalVal: '25 min a 180°C',
+ pptSlideTitle: 'Receta exquisita: Tarta fina de manzana',
+ pptCard1Title: 'Ingredientes clave',
+ pptCard1Text: 'Masa de hojaldre, 4 manzanas Golden, mantequilla dulce derretida, azúcar avainillado.',
+ pptCard2Title: 'Horneado y acabado',
+ pptCard2Text: 'Disposición en espiral apretada, hornear 25 min a 180°C hasta que esté dorada.',
+ guaranteeText: 'Estructura original 100% preservada',
+ },
+];
+
+const FORMAT_CONFIG: Record<
+ OfficeFormat,
+ {
+ ext: string;
+ label: string;
+ appTitle: string;
+ accentColor: string;
+ headerBg: string;
+ headerText: string;
+ statusBg: string;
+ icon: typeof FileText;
+ defaultFile: string;
+ }
+> = {
+ docx: {
+ ext: '.DOCX',
+ label: 'Word',
+ appTitle: 'Word',
+ accentColor: '#185ABD',
+ headerBg: 'bg-[#185ABD] dark:bg-[#10438F]',
+ headerText: 'text-white',
+ statusBg: 'bg-[#185ABD] dark:bg-[#10438F]',
+ icon: FileText,
+ defaultFile: 'Recette_Tarte_Pommes.docx',
+ },
+ xlsx: {
+ ext: '.XLSX',
+ label: 'Excel',
+ appTitle: 'Excel',
+ accentColor: '#107C41',
+ headerBg: 'bg-[#107C41] dark:bg-[#0B5C30]',
+ headerText: 'text-white',
+ statusBg: 'bg-[#107C41] dark:bg-[#0B5C30]',
+ icon: FileSpreadsheet,
+ defaultFile: 'Fiche_Technique_Recette.xlsx',
+ },
+ pptx: {
+ ext: '.PPTX',
+ label: 'PowerPoint',
+ appTitle: 'PowerPoint',
+ accentColor: '#C43E1C',
+ headerBg: 'bg-[#C43E1C] dark:bg-[#992F13]',
+ headerText: 'text-white',
+ statusBg: 'bg-[#C43E1C] dark:bg-[#992F13]',
+ icon: Presentation,
+ defaultFile: 'Presentation_Recette.pptx',
+ },
+ pdf: {
+ ext: '.PDF',
+ label: 'PDF Reader',
+ appTitle: 'Adobe Acrobat Reader',
+ accentColor: '#E03E2D',
+ headerBg: 'bg-[#2A2A2A] dark:bg-[#1A1A1A]',
+ headerText: 'text-white',
+ statusBg: 'bg-[#2A2A2A] dark:bg-[#1A1A1A]',
+ icon: FileCheck,
+ defaultFile: 'Document_Recette_Officiel.pdf',
+ },
+};
+
+const CYCLE_DURATION_MS = 6200; // ~6.2 secondes par cycle
+const SCAN_DURATION_MS = 1400; // 1.4s onde dorée
+
+/**
+ * Rendu intérieur d'un document Word (.docx) avec vraie mise en page A4
+ */
+function WordDocumentSheet({ data }: { data: RecipeData }) {
return (
-
- {[0, 1, 2].map((c) => (
-
-
-
-
+
+ {/* En-tête avec repères de marge Word */}
+
+
+
+
+ Style : Document Word Standard
+
+
+ Section 1 • Marges normales (2,5 cm)
+
- ))}
-
- );
-}
-function MiniChart() {
- const heights = [42, 68, 52, 84];
- return (
-
- {heights.map((h, i) => (
-
- ))}
-
- );
-}
-
-function DocumentCard({
- side,
- heading,
- widths,
- tag,
-}: {
- side: 'source' | 'target';
- heading: string;
- widths: readonly number[];
- tag: string;
-}) {
- return (
-
-
- {tag}
-
-
-
- {heading}
-
-
-
-
-
-
+
+
+ {data.docTitle}
+
+
+ {data.category}
+
-
-
+
+
+ {data.docSubtitle}
+
+
+ {/* Métadonnées */}
+
+
+
+ {data.timeLabel}
+
+
•
+
+
+ {data.servingsLabel}
+
+
•
+
+
+ {data.heatLabel}
+
-
+
- {/* one shimmer sweep per language change — the "translation" moment */}
- {side === 'target' && (
-
- )}
-
- );
-}
-
-export function HeroVisual() {
- const prefersReduced = useReducedMotion();
- const [index, setIndex] = useState(0);
-
- useEffect(() => {
- if (prefersReduced) return;
- const id = setInterval(() => setIndex((i) => (i + 1) % TARGETS.length), CYCLE_MS);
- return () => clearInterval(id);
- }, [prefersReduced]);
-
- const target = TARGETS[index];
-
- return (
-
-
-
-
-
- {/* connector: the translation itself, dots travelling left to right */}
-
-
-
-
-
- {!prefersReduced && (
-
- {[0, 1, 2].map((d) => (
-
- ))}
-
- )}
-
-
- {target.code}
-
-
+ {/* Corps 2 colonnes */}
+
+ {/* Colonne Ingrédients + Tableau Word */}
+
+
+
+
+ {data.ingredientsHeading}
+
+
+ {data.ingredients.map((item, idx) => (
+
+
+ {item.name}
+
+
+ {item.amount}
+
+
+ ))}
+
-
+ {/* Vrai Tableau Word avec bordures officielles */}
+
+
+ {data.tableTitle}
+
+
+ {data.tableHeaders.map((header, hIdx) => (
+
+
+ {header}
+
+
+ {data.tableValues[hIdx]}
+
+
+ ))}
+
+
+
+
+ {/* Colonne Instructions numérotées Word */}
+
+
+
+
+ {data.stepsHeading}
+
+
+ {data.steps.map((st, sIdx) => (
+
+
+ {st.num}
+
+
+
+ {st.title}
+
+
+ {st.desc}
+
+
+
+ ))}
+
+
+
+
+
+ {/* Pied de page Word */}
+
+
+
+ {data.guaranteeText}
+
+
+ Wordly Engine • Microsoft Word XML
);
}
+
+/**
+ * Rendu intérieur d'une feuille Excel (.xlsx) avec vraie grille de cellules
+ */
+function ExcelDocumentSheet({ data }: { data: RecipeData }) {
+ return (
+
+ {/* Barre de formule Excel */}
+
+
+ A1:D8
+
+ fx
+
+ {data.excelFormula}
+
+
+
+ {/* Grille Excel avec entêtes de colonnes A, B, C, D */}
+
+ {/* Ligne des colonnes A B C D */}
+
+
#
+
A ({data.excelHeaders[0]})
+
B ({data.excelHeaders[1]})
+
C ({data.excelHeaders[2]})
+
D ({data.excelHeaders[3]})
+
+
+ {/* Ligne 1 : Titre fusionné */}
+
+
+ 1
+
+
+ {data.docTitle}
+
+
+
+ {/* Lignes de données Excel */}
+ {data.excelRows.map((row, rIdx) => (
+
+
+ {rIdx + 2}
+
+
+ {row[0]}
+
+
+ {row[1]}
+
+
+ {row[2]}
+
+
+ {row[3]}
+
+
+ ))}
+
+ {/* Ligne de total / formule */}
+
+
+ 7
+
+
+ {data.excelTotalLabel}
+
+
+ {data.excelTotalVal}
+
+
+
+
+ {/* Onglets de classeur Excel en bas */}
+
+
+ Feuil1_Recette
+
+
+ Feuil2_Calculs
+
+
+
+ Formules & Cellules 100% intactes
+
+
+
+ );
+}
+
+/**
+ * Rendu intérieur d'une diapositive PowerPoint (.pptx)
+ */
+function PptxDocumentSheet({ data }: { data: RecipeData }) {
+ return (
+
+ {/* Diapositive 16:9 avec cartouche PowerPoint */}
+
+
+
+ Diapositive 1 / 1
+
+
+ {data.pptSlideTitle}
+
+
+
+ Thème Officiel 16:9
+
+
+
+ {/* Cartes de contenu de présentation */}
+
+
+
+
+ {data.pptCard1Title}
+
+
+ {data.pptCard1Text}
+
+
+
+
+
+
+ {data.pptCard2Title}
+
+
+ {data.pptCard2Text}
+
+
+
+
+
+
+
+ Blocs textes & vecteurs PowerPoint préservés
+
+
+ Affichage Normal • 100%
+
+
+
+ );
+}
+
+/**
+ * Rendu intérieur d'un document PDF vectoriel officiel
+ */
+function PdfDocumentSheet({ data }: { data: RecipeData }) {
+ return (
+
+ {/* Barre d'outils Acrobat */}
+
+
+
+ PDF/A-1b
+
+ Page 1 / 1
+
+
+
+
+ {/* Page PDF avec repères de coupe */}
+
+
+
+
+ {data.docTitle}
+
+
+ DOCUMENT CERTIFIÉ
+
+
+
+
+ {data.docSubtitle} — {data.timeLabel} • {data.servingsLabel} • {data.heatLabel}.
+
+
+
+
+
{data.ingredientsHeading}
+ {data.ingredients.slice(0, 3).map((ing, i) => (
+
+ {ing.name}
+ {ing.amount}
+
+ ))}
+
+
+
{data.stepsHeading}
+ {data.steps.slice(0, 2).map((st, i) => (
+
+ {st.num}. {st.desc}
+
+ ))}
+
+
+
+
+
+ Adobe PostScript • Calque vectoriel préservé
+ 100% Fidélité PDF
+
+
+
+ );
+}
+
+/**
+ * Composant principal de démonstration
+ */
+export function HeroVisual() {
+ const prefersReduced = useReducedMotion();
+ const [format, setFormat] = useState
('docx');
+ const [langIndex, setLangIndex] = useState(0);
+ const [prevLangIndex, setPrevLangIndex] = useState(0);
+ const [isTranslating, setIsTranslating] = useState(false);
+ const [scanProgress, setScanProgress] = useState(0);
+
+ const cfg = FORMAT_CONFIG[format];
+ const currentRecipe = RECIPES[langIndex];
+ const prevRecipe = RECIPES[prevLangIndex];
+
+ // Déclencher une traduction manuelle ou automatique
+ const triggerTranslation = useCallback((targetLangIndex: number) => {
+ if (targetLangIndex === langIndex || isTranslating) return;
+
+ setPrevLangIndex(langIndex);
+ setLangIndex(targetLangIndex);
+ setIsTranslating(true);
+ setScanProgress(0);
+
+ const startTime = performance.now();
+ const animateScan = (currentTime: number) => {
+ const elapsed = currentTime - startTime;
+ const progress = Math.min(elapsed / SCAN_DURATION_MS, 1);
+ setScanProgress(progress * 100);
+
+ if (progress < 1) {
+ requestAnimationFrame(animateScan);
+ } else {
+ setIsTranslating(false);
+ setPrevLangIndex(targetLangIndex);
+ }
+ };
+
+ requestAnimationFrame(animateScan);
+ }, [langIndex, isTranslating]);
+
+ // Boucle de rotation automatique des langues (toutes les ~6.2 secondes)
+ useEffect(() => {
+ if (prefersReduced) return;
+
+ const interval = setInterval(() => {
+ setLangIndex((current) => {
+ const next = (current + 1) % RECIPES.length;
+ setPrevLangIndex(current);
+ setIsTranslating(true);
+ setScanProgress(0);
+
+ const startTime = performance.now();
+ const animateScan = (currentTime: number) => {
+ const elapsed = currentTime - startTime;
+ const progress = Math.min(elapsed / SCAN_DURATION_MS, 1);
+ setScanProgress(progress * 100);
+
+ if (progress < 1) {
+ requestAnimationFrame(animateScan);
+ } else {
+ setIsTranslating(false);
+ setPrevLangIndex(next);
+ }
+ };
+
+ requestAnimationFrame(animateScan);
+ return next;
+ });
+ }, CYCLE_DURATION_MS);
+
+ return () => clearInterval(interval);
+ }, [prefersReduced]);
+
+ // Sélecteur de rendu selon le format actif
+ const renderSheet = (data: RecipeData) => {
+ switch (format) {
+ case 'xlsx':
+ return ;
+ case 'pptx':
+ return ;
+ case 'pdf':
+ return ;
+ case 'docx':
+ default:
+ return ;
+ }
+ };
+
+ return (
+
+ {/* Conteneur de fenêtre logicielle Office / PDF ultra-réaliste */}
+
+
+ {/* ============================================================ */}
+ {/* 1. BARRE DE TITRE OFFICIELLE DU LOGICIEL (Word/Excel/PPT/PDF) */}
+ {/* ============================================================ */}
+
+
+ {/* Gauche : Icônes d'accès rapide (Enregistrer, Annuler, Rétablir) */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {cfg.defaultFile} — {cfg.appTitle}
+
+
+
+ {/* Centre : Recherche Office */}
+
+
+ Rechercher ou demander de l'aide (Alt+Q)
+
+
+ {/* Droite : Boutons de fenêtre Windows / Mac */}
+
+
+
+
+
+
+
+ {/* ============================================================ */}
+ {/* 2. RUBAN OFFICE / SÉLECTEUR DE FORMAT RÉEL */}
+ {/* ============================================================ */}
+
+
+ {/* Onglets des 4 formats bureautiques supportés */}
+
+ {(['docx', 'xlsx', 'pptx', 'pdf'] as const).map((fmtKey) => {
+ const item = FORMAT_CONFIG[fmtKey];
+ const isSelected = format === fmtKey;
+ const Icon = item.icon;
+ return (
+ setFormat(fmtKey)}
+ className={cn(
+ 'flex items-center gap-1.5 px-2.5 py-1 rounded text-[11.5px] font-bold font-mono transition-all duration-200 cursor-pointer border',
+ isSelected
+ ? 'bg-white dark:bg-[#303030] text-[#1A1A1A] dark:text-white border-black/15 dark:border-white/20 shadow-xs ring-1 ring-black/5'
+ : 'border-transparent text-[#1A1A1A]/60 dark:text-white/60 hover:bg-black/5 dark:hover:bg-white/5'
+ )}
+ title={`Basculer vers le format ${item.label}`}
+ >
+
+ {item.ext}
+
+ );
+ })}
+
+
+ {/* Outils du ruban (Police, Gras, Italique, Alignement) */}
+
+
+ {format === 'docx' ? 'Calibri' : format === 'xlsx' ? 'Segoe UI' : 'Arial'}
+
+
+
+
+
+
+
+
+
+
+
+ {/* Sélecteur de langue en direct */}
+
+
+ Langue :
+
+
+ {RECIPES.map((r, rIdx) => {
+ const isActive = rIdx === langIndex;
+ return (
+ triggerTranslation(rIdx)}
+ className={cn(
+ 'px-2 py-0.5 rounded text-[11px] font-bold font-mono transition-all duration-200 cursor-pointer',
+ isActive
+ ? 'bg-[#C5A17A] text-white shadow-xs'
+ : 'text-[#1A1A1A]/60 dark:text-white/60 hover:text-[#1A1A1A] dark:hover:text-white'
+ )}
+ >
+ {r.lang}
+
+ );
+ })}
+
+
+
+
+ {/* ============================================================ */}
+ {/* 3. RÈGLE GRADUÉE WORD (Visible en mode .docx) */}
+ {/* ============================================================ */}
+ {format === 'docx' && (
+
+ ◄ 0
+ 2
+ 4
+ 6
+ 8
+ 10
+ 12
+ 14
+ 16 cm ►
+
+ )}
+
+ {/* ============================================================ */}
+ {/* 4. PLAN DE TRAVAIL & DOCUMENT A4 FLOTTANT */}
+ {/* ============================================================ */}
+
+
+ {/* Feuille de document flottante avec ombre de papier réelle */}
+
+
+ {/* Conteneur Superposé Grid garantissant 0px de décalage */}
+
+
+ {/* Couche 1 : Document de base (Langue précédente ou active) */}
+
+ {renderSheet(isTranslating ? prevRecipe : currentRecipe)}
+
+
+ {/* Couche 2 : Document traduit révélé par découpe (Clip-path) */}
+ {isTranslating && !prefersReduced && (
+
+ {renderSheet(currentRecipe)}
+
+ )}
+
+ {/* ============================================================ */}
+ {/* 5. ONDE DORÉE DE NUMÉRISATION / TRADUCTION */}
+ {/* ============================================================ */}
+ {isTranslating && !prefersReduced && (
+
+ {/* Halo lumineux doré au-dessus */}
+
+
+ {/* Ligne laser dorée principale */}
+
+
+ {/* Badge central d'indication de traduction */}
+
+
+ Traduction en direct : {prevRecipe.lang} → {currentRecipe.lang}
+
+
+ )}
+
+
+
+
+ {/* ============================================================ */}
+ {/* 6. BARRE D'ÉTAT OFFICIELLE EN BAS (Statistiques & Langue) */}
+ {/* ============================================================ */}
+
+
+ {/* Statistiques documentaires officielles */}
+
+ Page 1 sur 1
+ |
+ 184 mots
+ |
+
+ {currentRecipe.langName}
+
+
+
+ {/* Sceau de conformité Wordly.art */}
+
+
+ Mise en page 100% inviolée :
+ 0 pixel de décalage
+
+
+ {/* Indicateur de Zoom */}
+
+ Mode Page
+ 100%
+
+
+
+
+
+ );
+}
diff --git a/translators/word_translator.py b/translators/word_translator.py
index 96f285d..0b46ff3 100644
--- a/translators/word_translator.py
+++ b/translators/word_translator.py
@@ -1378,7 +1378,7 @@ class WordTranslator:
is translated whole — not fragment by fragment — and the result is
written into the first run while the sibling runs are blanked.
This is what keeps mid-sentence bold spans ("This is *very*
- important") coherent in the target language, like DeepL's inline
+ important") coherent in the target language, keeping inline
tag handling.
Note: python-docx's `paragraph.runs` only returns DIRECT child