diff --git a/frontend/src/components/landing/hero-visual.tsx b/frontend/src/components/landing/hero-visual.tsx new file mode 100644 index 0000000..c436610 --- /dev/null +++ b/frontend/src/components/landing/hero-visual.tsx @@ -0,0 +1,187 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { motion, useReducedMotion, AnimatePresence } from 'framer-motion'; +import { Languages } 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. + */ + +const SOURCE_HEADING = 'Rapport annuel'; + +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) => ( + + ))} +
+ ); +} + +function MiniTable() { + return ( +
+ {[0, 1, 2].map((c) => ( +
+
+
+
+
+ ))} +
+ ); +} + +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} +

+ + + +
+ +
+
+ +
+
+ + {/* 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} + + +
+ + +
+
+
+ ); +} diff --git a/frontend/src/components/landing/landing-page.tsx b/frontend/src/components/landing/landing-page.tsx index e3e08c3..881f9ca 100644 --- a/frontend/src/components/landing/landing-page.tsx +++ b/frontend/src/components/landing/landing-page.tsx @@ -23,10 +23,10 @@ import { Languages, Monitor, Eye, - Activity, } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import { useI18n } from "@/lib/i18n"; +import { HeroVisual } from "./hero-visual"; import { API_BASE } from "@/lib/config"; import { LanguageSwitcher } from "@/components/ui/language-switcher"; @@ -134,17 +134,10 @@ const Hero = () => { transition={{ duration: 1.2, ease: [0.16, 1, 0.3, 1], delay: 0.2 }} className="relative max-w-5xl mx-auto" > -
-
- Workspace -
-
-
+ +

+ {t('landing.hero.visualCaption')} +

diff --git a/frontend/src/lib/i18n/messages/en/landing.json b/frontend/src/lib/i18n/messages/en/landing.json index 534f949..b9f5d66 100644 --- a/frontend/src/lib/i18n/messages/en/landing.json +++ b/frontend/src/lib/i18n/messages/en/landing.json @@ -149,5 +149,6 @@ "landing.translate.aiAnalysis": "Active AI Analysis", "landing.translate.processing": "Processing", "landing.translate.preservingLayout": "Your layout is being preserved", - "landing.formats.pill": "COMPATIBILITY" + "landing.formats.pill": "COMPATIBILITY", + "landing.hero.visualCaption": "Same layout, new language — nothing else moves" } diff --git a/frontend/src/lib/i18n/messages/fr/landing.json b/frontend/src/lib/i18n/messages/fr/landing.json index 95808eb..b87fcf4 100644 --- a/frontend/src/lib/i18n/messages/fr/landing.json +++ b/frontend/src/lib/i18n/messages/fr/landing.json @@ -149,5 +149,6 @@ "landing.translate.aiAnalysis": "Analyse IA Active", "landing.translate.processing": "Traitement en cours", "landing.translate.preservingLayout": "Votre mise en page est en cours de préservation", - "landing.formats.pill": "COMPATIBILITÉ" + "landing.formats.pill": "COMPATIBILITÉ", + "landing.hero.visualCaption": "Même mise en page, nouvelle langue — rien d'autre ne bouge" }