feat: complete editorial redesign of Wordly.art SaaS
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m31s

New design system with cream/dark/copper palette (#FDFCF9, #1A1A1A, #C5A17A),
Inter font, rounded editorial cards, and dark mode support.

- globals.css: Rewritten from 1867→200 lines, new brand colors + editorial utilities
- Landing page: New editorial design with Hero, Steps, Features, Layout, Formats, Pricing, CTA, Footer
- Auth pages: Editorial card design with decorative blur blobs and back-to-home links
- Dashboard sidebar: New w-72 sidebar with Momento promo section
- Dashboard header: New h-20 topbar with editorial styling
- i18n: Added memento/common/dashboard keys for all 13 locales, cleaned duplicate landing keys

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 12:03:49 +02:00
parent 18e1aca148
commit b2b6f03399
10 changed files with 2583 additions and 4180 deletions

View File

@@ -1,21 +1,24 @@
'use client';
import { Suspense } from 'react';
import Link from 'next/link';
import { LoginForm } from './LoginForm';
import { Loader2, Languages } from 'lucide-react';
import { Loader2 } from 'lucide-react';
import { ChevronLeft } from 'lucide-react';
import { motion } from 'framer-motion';
import { useI18n } from '@/lib/i18n';
function LoadingFallback() {
const { t } = useI18n();
return (
<div className="w-full max-w-md mx-auto">
<div className="rounded-xl bg-card border border-border shadow-lg p-8">
<div className="editorial-card p-8">
<div className="flex flex-col items-center justify-center py-8 space-y-4">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary text-primary-foreground">
<Languages className="h-6 w-6" />
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-brand-dark text-white">
<span className="text-xl font-bold">W</span>
</div>
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<p className="text-muted-foreground">{t('common.loading')}</p>
<Loader2 className="h-8 w-8 animate-spin text-brand-accent" />
<p className="text-brand-dark/50 dark:text-white/50 text-sm">{t('common.loading')}</p>
</div>
</div>
</div>
@@ -23,23 +26,43 @@ function LoadingFallback() {
}
export default function LoginPage() {
const { t } = useI18n();
return (
<div className="min-h-screen bg-gradient-to-br from-surface via-surface-elevated to-background flex items-center justify-center p-4 relative overflow-hidden">
<div className="absolute inset-0">
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-accent/5" />
<div className="absolute inset-0 bg-[url('/grid.svg')] opacity-5" />
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/90 to-background/50" />
</div>
<div className="min-h-screen bg-brand-bg dark:bg-[#0a0a0a] flex items-center justify-center p-4 relative overflow-hidden">
{/* Decorative blur blobs */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-20 left-10 w-32 h-32 bg-primary/5 rounded-full blur-3xl animate-pulse" />
<div className="absolute bottom-20 right-20 w-24 h-24 bg-accent/5 rounded-full blur-2xl animate-pulse" />
<div className="absolute top-1/2 right-1/4 w-16 h-16 bg-success/5 rounded-full blur-xl animate-pulse" />
<div className="absolute top-1/4 left-1/4 w-[500px] h-[500px] bg-brand-accent/10 rounded-full blur-3xl" />
<div className="absolute bottom-1/4 right-1/4 w-[400px] h-[400px] bg-brand-dark/5 dark:bg-brand-accent/5 rounded-full blur-3xl" />
</div>
<Suspense fallback={<LoadingFallback />}>
<LoginForm />
</Suspense>
{/* Back to home */}
<motion.div
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, ease: 'easeOut' }}
className="absolute top-6 left-6 z-20"
>
<Link
href="/"
className="inline-flex items-center gap-2 text-brand-dark/50 dark:text-white/50 hover:text-brand-dark dark:hover:text-white transition-colors text-[11px] font-bold uppercase tracking-widest"
>
<ChevronLeft size={16} />
{t('common.backToHome')}
</Link>
</motion.div>
{/* Form container */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: 'easeOut', delay: 0.1 }}
className="relative z-10 w-full max-w-md"
>
<Suspense fallback={<LoadingFallback />}>
<LoginForm />
</Suspense>
</motion.div>
</div>
);
}

View File

@@ -1,20 +1,24 @@
'use client';
import { Languages, Loader2 } from 'lucide-react';
import { Suspense } from 'react';
import Link from 'next/link';
import { RegisterForm } from './RegisterForm';
import { Loader2 } from 'lucide-react';
import { ChevronLeft } from 'lucide-react';
import { motion } from 'framer-motion';
import { useI18n } from '@/lib/i18n';
function LoadingFallback() {
const { t } = useI18n();
return (
<div className="w-full max-w-md mx-auto">
<div className="rounded-xl bg-card border border-border shadow-lg p-8">
<div className="editorial-card p-8">
<div className="flex flex-col items-center justify-center py-8 space-y-4">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary text-primary-foreground">
<Languages className="h-6 w-6" />
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-brand-dark text-white">
<span className="text-xl font-bold">W</span>
</div>
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<p className="text-muted-foreground">{t('common.loading')}</p>
<Loader2 className="h-8 w-8 animate-spin text-brand-accent" />
<p className="text-brand-dark/50 dark:text-white/50 text-sm">{t('common.loading')}</p>
</div>
</div>
</div>
@@ -22,26 +26,43 @@ function LoadingFallback() {
}
export default function RegisterPage() {
const { t } = useI18n();
return (
<div className="min-h-screen bg-gradient-to-br from-surface via-surface-elevated to-background flex items-center justify-center p-4 relative overflow-hidden">
{/* Background gradient */}
<div className="absolute inset-0">
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-accent/5" />
<div className="absolute inset-0 bg-[url('/grid.svg')] opacity-5" />
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/90 to-background/50" />
</div>
{/* Decorative floating elements */}
<div className="min-h-screen bg-brand-bg dark:bg-[#0a0a0a] flex items-center justify-center p-4 relative overflow-hidden">
{/* Decorative blur blobs */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-20 left-10 w-20 h-20 bg-primary/10 rounded-full blur-xl animate-pulse" />
<div className="absolute top-40 right-20 w-32 h-32 bg-accent/10 rounded-full blur-2xl animate-pulse" />
<div className="absolute bottom-20 left-1/4 w-16 h-16 bg-success/10 rounded-full blur-lg animate-pulse" />
<div className="absolute top-1/4 left-1/4 w-[500px] h-[500px] bg-brand-accent/10 rounded-full blur-3xl" />
<div className="absolute bottom-1/4 right-1/4 w-[400px] h-[400px] bg-brand-dark/5 dark:bg-brand-accent/5 rounded-full blur-3xl" />
</div>
{/* Form — Suspense required by useSearchParams() in useRegister */}
<div className="relative z-10 w-full max-w-md">
<RegisterForm />
</div>
{/* Back to home */}
<motion.div
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, ease: 'easeOut' }}
className="absolute top-6 left-6 z-20"
>
<Link
href="/"
className="inline-flex items-center gap-2 text-brand-dark/50 dark:text-white/50 hover:text-brand-dark dark:hover:text-white transition-colors text-[11px] font-bold uppercase tracking-widest"
>
<ChevronLeft size={16} />
{t('common.backToHome')}
</Link>
</motion.div>
{/* Form container */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: 'easeOut', delay: 0.1 }}
className="relative z-10 w-full max-w-md"
>
<Suspense fallback={<LoadingFallback />}>
<RegisterForm />
</Suspense>
</motion.div>
</div>
);
}

View File

@@ -3,17 +3,12 @@
import { useState } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import {
Languages,
Menu,
X,
import {
Menu,
X,
LogOut
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
import { useUser } from './useUser';
import { useLogout } from './useLogout';
import { getNavItems } from './constants';
@@ -32,62 +27,78 @@ export function DashboardHeader() {
return (
<>
<header className="flex h-14 shrink-0 items-center justify-between border-b border-border bg-card px-4 lg:px-6">
<header className="flex h-20 shrink-0 items-center justify-between border-b border-black/5 dark:border-white/5 bg-white/50 dark:bg-[#141414]/50 backdrop-blur-md px-6 lg:px-8">
{/* Mobile menu button */}
<Button
variant="ghost"
size="icon"
className="lg:hidden"
<button
className="lg:hidden p-2 text-brand-dark/40 dark:text-white/40 hover:text-brand-dark dark:hover:text-white transition-colors"
onClick={() => setMobileOpen(!mobileOpen)}
aria-label={t('dashboard.header.toggleMenu')}
>
{mobileOpen ? <X className="size-4" /> : <Menu className="size-4" />}
</Button>
{mobileOpen ? <X size={20} /> : <Menu size={20} />}
</button>
{/* Mobile brand */}
<div className="flex items-center gap-2 lg:hidden">
<div className="flex size-6 items-center justify-center rounded-md bg-foreground">
<Languages className="size-3 text-background" />
<div className="flex size-7 items-center justify-center rounded-xl bg-brand-dark text-white font-bold text-xs">
W
</div>
<span className="text-sm font-semibold text-foreground">{t('auth.brandName')}</span>
<span className="text-sm font-bold text-brand-dark dark:text-white">{t('auth.brandName')}</span>
</div>
{/* Page title - desktop */}
<div className="hidden items-center gap-3 lg:flex">
<h1 className="text-sm font-semibold text-foreground">{t('dashboard.header.title')}</h1>
{/* Left side - desktop */}
<div className="hidden lg:flex items-center gap-3">
<span className="text-[10px] font-bold uppercase tracking-[0.4em] text-brand-dark/30 dark:text-white/30">
{t('dashboard.topbar.interfaceLabel')}
</span>
</div>
{/* Right side */}
<div className="flex items-center gap-2">
<ThemeToggle />
<div className="flex items-center gap-3">
{/* Theme toggle */}
<div className="hidden lg:block p-3 bg-brand-muted dark:bg-[#1f1f1f] rounded-xl">
<ThemeToggle />
</div>
<div className="hidden lg:block h-6 w-px bg-black/5 dark:bg-white/5" />
{/* User info */}
{!isLoading && user && (
<>
<Badge
variant="secondary"
className={cn(
'border border-accent/20',
user.tier === 'free' || !user.tier
? 'bg-muted text-muted-foreground'
: 'bg-accent/10 text-accent'
)}
>
{translateTier(t, user.tier)}
</Badge>
<div className="hidden lg:flex items-center gap-3">
<div className="flex flex-col items-end gap-0.5">
<span className="text-[11px] font-black uppercase tracking-tight text-brand-dark dark:text-white">
{user.name}
</span>
<span className="text-[9px] font-black uppercase tracking-widest text-brand-accent">
{user.tier && user.tier !== 'free'
? t('dashboard.topbar.premiumAccess', { defaultValue: 'Premium Access' })
: translateTier(t, user.tier)}
</span>
</div>
<Link href="/dashboard/profile" title={t('dashboard.header.profileTitle')}>
<Avatar className="size-8 cursor-pointer ring-2 ring-transparent hover:ring-accent/40 transition-all">
<AvatarFallback className="bg-accent text-accent-foreground text-xs font-semibold">
{getInitials(user.name)}
</AvatarFallback>
</Avatar>
<div className="flex size-9 items-center justify-center rounded-xl bg-brand-dark dark:bg-brand-accent text-white dark:text-brand-dark font-bold text-[11px] cursor-pointer hover:shadow-lg transition-shadow">
{getInitials(user.name)}
</div>
</Link>
</>
</div>
)}
{/* Mobile theme + avatar */}
{!isLoading && user && (
<div className="flex lg:hidden items-center gap-2">
<ThemeToggle />
<Link href="/dashboard/profile" title={t('dashboard.header.profileTitle')}>
<div className="flex size-8 items-center justify-center rounded-xl bg-brand-dark dark:bg-brand-accent text-white dark:text-brand-dark font-bold text-[10px]">
{getInitials(user.name)}
</div>
</Link>
</div>
)}
</div>
</header>
{/* Mobile navigation drawer */}
{mobileOpen && (
<div className="border-b border-border bg-card px-4 py-3 lg:hidden">
<div className="border-b border-black/5 dark:border-white/5 bg-white dark:bg-[#141414] px-6 py-4 lg:hidden">
<nav className="flex flex-col gap-1">
{navItems.map((item) => {
const isActive = pathname === item.href;
@@ -97,48 +108,41 @@ export function DashboardHeader() {
href={item.href}
onClick={() => setMobileOpen(false)}
className={cn(
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
'flex items-center gap-4 rounded-2xl px-6 py-4 text-[11px] font-black uppercase tracking-[0.2em] transition-all duration-200',
isActive
? 'bg-secondary text-foreground'
: 'text-muted-foreground hover:bg-secondary/60 hover:text-foreground'
? 'bg-brand-dark text-white shadow-xl'
: 'text-brand-dark/40 dark:text-white/40 hover:bg-brand-muted dark:hover:bg-[#1f1f1f]'
)}
>
<item.icon className="size-4 shrink-0" />
<item.icon size={18} className="shrink-0" />
{t(item.labelKey)}
</Link>
);
})}
<Separator className="my-2" />
<div className="my-3 h-px bg-black/5 dark:bg-white/5" />
{!isLoading && user && (
<Link
href="/dashboard/profile"
onClick={() => setMobileOpen(false)}
className="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-secondary/60 transition-colors"
>
<Avatar className="size-8">
<AvatarFallback className="bg-accent text-accent-foreground text-xs font-semibold">
{getInitials(user.name)}
</AvatarFallback>
</Avatar>
<div className="flex flex-col gap-0.5">
<span className="text-sm font-medium text-foreground">{user.name}</span>
<Badge
variant="secondary"
className={cn(
'text-xs w-fit',
user.tier === 'pro' && 'border border-accent/20 bg-accent/10 text-accent'
)}
>
{translateTier(t, user.tier)}
</Badge>
<div className="flex items-center gap-3 px-4 py-3">
<div className="flex size-10 items-center justify-center rounded-xl bg-brand-dark dark:bg-brand-accent text-white dark:text-brand-dark font-bold text-[11px]">
{getInitials(user.name)}
</div>
</Link>
<div className="flex flex-col gap-0.5">
<span className="text-[11px] font-black uppercase tracking-tight text-brand-dark dark:text-white">
{user.name}
</span>
<span className="px-1.5 py-0 rounded-full border border-brand-accent/30 text-brand-accent text-[7px] font-black uppercase w-fit">
{translateTier(t, user.tier)}
</span>
</div>
</div>
)}
<button
onClick={logout}
className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-secondary/60 hover:text-foreground"
className="flex items-center gap-3 rounded-2xl px-6 py-4 text-[11px] font-black uppercase tracking-[0.2em] text-brand-dark/40 dark:text-white/40 hover:text-red-500 transition-colors"
>
<LogOut className="size-4 shrink-0" />
<LogOut size={18} className="shrink-0" />
{t('dashboard.sidebar.signOut')}
</button>
</nav>

View File

@@ -22,17 +22,19 @@ export function DashboardLayoutClient({ children }: { children: React.ReactNode
if (!mounted || !isAuthenticated) {
return (
<div className="flex h-screen items-center justify-center bg-background">
<div className="flex h-screen items-center justify-center bg-brand-bg dark:bg-[#0a0a0a]">
<div className="text-center space-y-4">
<div className="animate-spin rounded-full h-8 w-8 border-4 border-muted border-t-foreground mx-auto"></div>
<p className="text-sm text-muted-foreground">Loading...</p>
<div className="animate-spin rounded-full h-8 w-8 border-4 border-brand-muted border-t-brand-accent mx-auto"></div>
<p className="text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/30 dark:text-white/30">
Loading...
</p>
</div>
</div>
);
}
return (
<div className="flex h-screen bg-background">
<div className="flex h-screen bg-brand-bg dark:bg-[#0a0a0a]">
<DashboardSidebar />
<div className="flex flex-1 flex-col overflow-hidden">
<DashboardHeader />

View File

@@ -2,12 +2,8 @@
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { Languages, LogOut } from 'lucide-react';
import { LogOut } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { Button } from '@/components/ui/button';
import { useUser } from './useUser';
import { useLogout } from './useLogout';
import { getNavItems } from './constants';
@@ -24,19 +20,19 @@ export function DashboardSidebar() {
const navItems = getNavItems(['pro', 'business', 'enterprise'].includes(user?.tier ?? ''));
return (
<aside className="hidden w-64 shrink-0 border-r border-border bg-card lg:flex lg:flex-col">
<aside className="hidden w-72 shrink-0 border-r border-black/5 dark:border-white/5 bg-white dark:bg-[#141414] lg:flex lg:flex-col">
{/* Brand */}
<div className="flex h-14 items-center gap-2.5 px-5">
<div className="flex size-7 items-center justify-center rounded-md bg-foreground">
<Languages className="size-3.5 text-background" />
<div className="flex items-center gap-3 px-6 py-6">
<div className="flex size-9 items-center justify-center rounded-xl bg-brand-dark text-white font-bold text-sm">
W
</div>
<span className="text-sm font-semibold tracking-tight text-foreground">{t('auth.brandName')}</span>
<span className="text-sm font-bold tracking-tight text-brand-dark dark:text-white">
{t('auth.brandName')}
</span>
</div>
<Separator />
{/* Navigation */}
<nav className="flex-1 overflow-y-auto px-3 py-4">
<nav className="flex-1 overflow-y-auto px-3 py-2">
<div className="flex flex-col gap-1">
{navItems.map((item) => {
const isActive = pathname === item.href;
@@ -45,13 +41,13 @@ export function DashboardSidebar() {
key={item.href}
href={item.href}
className={cn(
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
'flex items-center gap-4 rounded-2xl px-6 py-4 text-[11px] font-black uppercase tracking-[0.2em] transition-all duration-200',
isActive
? 'bg-secondary text-foreground'
: 'text-muted-foreground hover:bg-secondary/60 hover:text-foreground'
? 'bg-brand-dark text-white shadow-xl'
: 'text-brand-dark/40 dark:text-white/40 hover:bg-brand-muted dark:hover:bg-[#1f1f1f]'
)}
>
<item.icon className="size-4 shrink-0" />
<item.icon size={18} className="shrink-0" />
{t(item.labelKey)}
</Link>
);
@@ -59,54 +55,68 @@ export function DashboardSidebar() {
</div>
</nav>
{/* Bottom section: user + actions */}
<div className="mt-auto border-t border-border">
{/* User section */}
{/* Upgrade link for free users */}
{(!user?.tier || user.tier === 'free') && (
<div className="px-6 py-2">
<Link
href="/pricing"
className="text-[10px] font-black uppercase tracking-widest text-brand-accent hover:text-brand-dark dark:hover:text-white transition-colors"
>
{t('dashboard.sidebar.upgradeToPro', { defaultValue: 'Passer Pro →' })}
</Link>
</div>
)}
{/* Momento promo section */}
<div className="px-6 py-4">
<div className="bg-brand-muted/50 dark:bg-[#1f1f1f]/50 rounded-3xl p-6 border border-black/5 dark:border-white/5 group cursor-pointer hover:bg-brand-dark dark:hover:bg-brand-accent transition-all duration-500">
<div className="flex items-center gap-3 mb-4">
<div className="w-8 h-8 bg-brand-dark group-hover:bg-brand-accent rounded-lg flex items-center justify-center text-white text-xs font-black shadow-sm transition-all group-hover:rotate-12">M</div>
<span className="text-[10px] font-black uppercase tracking-widest text-brand-dark group-hover:text-white dark:text-white transition-colors">{t('memento.title')}</span>
</div>
<p className="text-[9px] text-brand-dark/40 group-hover:text-white/60 font-medium leading-relaxed mb-6">
{t('memento.slogan').substring(0, 80)}...
</p>
<button className="w-full py-2.5 bg-brand-dark group-hover:bg-white text-white group-hover:text-brand-dark dark:bg-[#272727] rounded-xl text-[8px] font-black uppercase tracking-widest shadow-sm transition-all">
{t('memento.ctaFree')}
</button>
</div>
</div>
{/* User section */}
<div className="border-t border-black/5 dark:border-white/5">
{!isLoading && user && (
<>
<div className="flex items-center gap-2.5 px-4 py-3">
<Avatar className="size-8 shrink-0">
<AvatarFallback className="bg-primary text-primary-foreground text-xs font-semibold">
{getInitials(user.name)}
</AvatarFallback>
</Avatar>
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
<span className="truncate text-sm font-medium leading-none text-foreground">{user.name}</span>
<span className="truncate text-xs leading-none text-muted-foreground">{user.email}</span>
<div className="flex items-center gap-1.5">
<Badge
variant="secondary"
className={cn(
'text-xs',
user.tier !== 'free' && user.tier && 'border border-primary/20 bg-primary/10 text-primary'
)}
>
{translateTier(t, user.tier)}
</Badge>
{(!user.tier || user.tier === 'free') && (
<Link href="/pricing" className="text-xs font-medium text-primary hover:underline">
{t('dashboard.sidebar.upgradeToPro', { defaultValue: 'Passer Pro →' })}
</Link>
)}
</div>
<div className="px-6 py-4">
<div className="flex items-center gap-3 mb-3">
<div className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-brand-dark dark:bg-brand-accent text-white dark:text-brand-dark font-bold text-[11px]">
{getInitials(user.name)}
</div>
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
<span className="truncate text-[11px] font-black uppercase tracking-tight text-brand-dark dark:text-white">
{user.name}
</span>
<span className="truncate text-[9px] text-brand-dark/40 dark:text-white/40">
{user.email}
</span>
</div>
<ThemeToggle />
</div>
<Separator />
</>
<div className="flex items-center gap-2 mb-3">
<span className="px-1.5 py-0 rounded-full border border-brand-accent/30 text-brand-accent text-[7px] font-black uppercase">
{translateTier(t, user.tier)}
</span>
</div>
</div>
)}
{/* Actions */}
<div className="px-3 py-3">
<Button
variant="ghost"
size="sm"
className="w-full justify-start gap-2 text-muted-foreground hover:text-foreground"
<div className="flex items-center gap-2 px-6 py-3">
<ThemeToggle />
<button
className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-brand-dark/40 dark:text-white/40 hover:text-red-500 transition-colors"
onClick={logout}
>
<LogOut className="size-3.5" />
<LogOut size={14} />
{t('dashboard.sidebar.signOut')}
</Button>
</button>
</div>
</div>
</aside>

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { LandingPage } from "@/components/landing/landing-page"
import LandingPage from "@/components/landing/landing-page"
export default function Home() {
return <LandingPage />

View File

@@ -1,131 +0,0 @@
"use client";
import { useTranslation } from "@/lib/i18n";
import { FileText } from "lucide-react";
/** Un seul exemple : même .docx, original EN à gauche, traduction FR à droite. */
const SOURCE_DOC = {
heading: "1. Scope and objectives",
content: (
<>
<p className="mb-2 leading-relaxed">
This document defines the <strong className="text-emerald-700 bg-emerald-50 px-1 rounded font-semibold border border-emerald-200">thermal performance requirements</strong> for the HVAC subsystem. All contractors must submit compliance certificates before commissioning.
</p>
<div className="my-3 border border-slate-200 rounded p-1 bg-white shadow-sm">
<img src="/revenue_chart.png" alt="Corporate Chart" className="w-full h-auto rounded" />
</div>
<p className="mb-2 leading-relaxed">
The design shall maintain indoor comfort per <span className="underline decoration-blue-400 decoration-2 font-medium">ISO 7730</span>. Deviations require written approval from the project engineer.
</p>
</>
),
};
const TARGET_DOC = {
heading: "1. Périmètre et objectifs",
content: (
<>
<p className="mb-2 leading-relaxed">
Le présent document définit les <strong className="text-emerald-700 bg-emerald-50 px-1 rounded font-semibold border border-emerald-200">exigences de performance thermique</strong> pour le sous-système CVC. Toutes les entreprises doivent fournir des certificats de conformité avant la mise en service.
</p>
<div className="my-3 border border-slate-200 rounded p-1 bg-white shadow-sm">
<img src="/revenue_chart.png" alt="Corporate Chart" className="w-full h-auto rounded" />
</div>
<p className="mb-2 leading-relaxed">
La conception doit garantir le confort intérieur conformément à <span className="underline decoration-blue-400 decoration-2 font-medium">l'ISO 7730</span>. Toute dérogation nécessite l'accord écrit de l'ingénieur de projet.
</p>
</>
),
};
export function HeroWordComparison() {
const { t } = useTranslation();
return (
<div className="relative w-full max-w-xl mx-auto lg:max-w-none lg:mx-0">
<div className="relative rounded-xl bg-[#1a1a1c] border border-[#27272A] shadow-2xl overflow-hidden ring-1 ring-white/5">
<div className="absolute -inset-px bg-gradient-to-br from-primary/20 via-transparent to-emerald-500/10 opacity-40 blur-xl pointer-events-none rounded-xl" />
<div className="relative flex items-center gap-2 px-3 py-2 bg-[#252526] border-b border-[#3c3c3c]">
<div className="flex gap-1.5" aria-hidden>
<span className="h-2.5 w-2.5 rounded-full bg-[#ff5f57]" />
<span className="h-2.5 w-2.5 rounded-full bg-[#febc2e]" />
<span className="h-2.5 w-2.5 rounded-full bg-[#28c840]" />
</div>
<div className="flex flex-1 items-center justify-center gap-2 min-w-0">
<FileText className="h-4 w-4 shrink-0 text-[#2b579a]" aria-hidden />
<span className="truncate text-xs text-slate-300 font-medium">
{t("landing.heroDoc.fileName")}
</span>
<span className="hidden sm:inline text-[10px] text-slate-500 uppercase tracking-wide shrink-0">
Word
</span>
</div>
</div>
<div className="relative flex items-center gap-3 px-3 py-1.5 bg-[#2d2d30] border-b border-[#3c3c3c] text-[10px] text-slate-400">
<span className="text-white font-medium">{t("landing.heroDoc.ribbonFile")}</span>
<span>{t("landing.heroDoc.ribbonHome")}</span>
<span>{t("landing.heroDoc.ribbonInsert")}</span>
<span>{t("landing.heroDoc.ribbonLayout")}</span>
<span className="ml-auto hidden sm:flex items-center gap-1 text-slate-500" aria-hidden>
<span className="px-1 rounded bg-[#3c3c3c] font-semibold text-slate-300">B</span>
<span className="px-1 rounded bg-[#3c3c3c] italic text-slate-300">I</span>
<span className="px-1 rounded bg-[#3c3c3c] underline text-slate-300">U</span>
</span>
</div>
<div className="relative p-2 sm:p-4 bg-[#1e1e1e]">
<p className="mb-3 text-center text-xs text-slate-400 sm:text-start sm:px-1">
{t("landing.heroDoc.caption")}
</p>
<div className="flex rounded-lg overflow-hidden border border-[#3c3c3c] shadow-inner min-h-[240px] sm:min-h-[300px]">
<div className="w-1/2 border-r border-[#c8c8c8] bg-[#f3f3f3] flex flex-col">
<div className="shrink-0 px-2 py-1.5 bg-[#e7e7e7] border-b border-[#d0d0d0]">
<span className="text-[10px] font-semibold uppercase tracking-wide text-[#444] truncate block">
{t("landing.heroDoc.badgeOriginal")}
</span>
</div>
<div className="flex-1 p-3 sm:p-4 overflow-hidden bg-white text-start">
<article
className="text-[11px] sm:text-xs leading-relaxed text-[#242424]"
style={{
fontFamily:
'Calibri, "Segoe UI", "Helvetica Neue", Arial, sans-serif',
}}
>
<h3 className="text-sm sm:text-base font-bold text-[#2b579a] mb-2">
{SOURCE_DOC.heading}
</h3>
{SOURCE_DOC.content}
</article>
</div>
</div>
<div className="w-1/2 bg-[#f0f7ff] flex flex-col">
<div className="shrink-0 px-2 py-1.5 bg-[#ddeaf7] border-b border-[#b4c7e7]">
<span className="text-[10px] font-semibold uppercase tracking-wide text-[#1a3a5c] truncate block">
{t("landing.heroDoc.badgeTranslated")}
</span>
</div>
<div className="flex-1 p-3 sm:p-4 overflow-hidden bg-white text-start">
<article
className="text-[11px] sm:text-xs leading-relaxed text-[#242424]"
style={{
fontFamily:
'Calibri, "Segoe UI", "Helvetica Neue", Arial, sans-serif',
}}
>
<h3 className="text-sm sm:text-base font-bold text-[#2b579a] mb-2">
{TARGET_DOC.heading}
</h3>
{TARGET_DOC.content}
</article>
</div>
</div>
</div>
</div>
</div>
</div>
);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff