740 lines
40 KiB
TypeScript
740 lines
40 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
import Image from 'next/image';
|
|
import Link from 'next/link';
|
|
import { useRouter } from 'next/navigation';
|
|
import { resolveImage } from '@/data/stitch-images';
|
|
import translations, { Language } from '@/data/translations';
|
|
import type { Segment } from '@/data/segments';
|
|
|
|
const LANGUAGES: { code: Language; label: string; name: string }[] = [
|
|
{ code: 'fr', label: 'FR', name: 'Français' },
|
|
{ code: 'en', label: 'EN', name: 'English' },
|
|
{ code: 'nl', label: 'NL', name: 'Nederlands' },
|
|
{ code: 'de', label: 'DE', name: 'Deutsch' },
|
|
];
|
|
|
|
function Reveal({
|
|
children,
|
|
delay = 0,
|
|
className = '',
|
|
}: {
|
|
children: React.ReactNode;
|
|
delay?: number;
|
|
className?: string;
|
|
}) {
|
|
const ref = React.useRef<HTMLDivElement>(null);
|
|
const [visible, setVisible] = React.useState(false);
|
|
|
|
React.useEffect(() => {
|
|
const el = ref.current;
|
|
if (!el) return;
|
|
const observer = new IntersectionObserver(
|
|
([entry]) => {
|
|
if (entry.isIntersecting) setVisible(true);
|
|
},
|
|
{ threshold: 0.12 }
|
|
);
|
|
observer.observe(el);
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className={className}
|
|
style={{
|
|
opacity: visible ? 1 : 0,
|
|
transform: visible ? 'translateY(0)' : 'translateY(22px)',
|
|
transition: `opacity 0.55s ease-out ${delay}ms, transform 0.55s ease-out ${delay}ms`,
|
|
}}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function SegmentPageClient({ data, lang }: { data: Segment; lang: Language }) {
|
|
const router = useRouter();
|
|
const setLang = (next: Language) => {
|
|
if (next !== lang) router.push(`/${next}/${data.slug}`);
|
|
};
|
|
const [isLangOpen, setIsLangOpen] = useState(false);
|
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
const [navVisible, setNavVisible] = useState(true);
|
|
const [openFaq, setOpenFaq] = useState<number | null>(null);
|
|
const [formData, setFormData] = useState({ firstName: '', lastName: '', email: '', message: '' });
|
|
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
|
const langDropdownRef = useRef<HTMLDivElement>(null);
|
|
const lastScrollY = useRef(0);
|
|
|
|
const t = translations[lang];
|
|
const s = data.translations[lang as keyof typeof data.translations];
|
|
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
const currentY = window.scrollY;
|
|
setNavVisible(currentY < lastScrollY.current || currentY < 10);
|
|
lastScrollY.current = currentY;
|
|
};
|
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
document.body.style.overflow = isMenuOpen ? 'hidden' : 'auto';
|
|
return () => { document.body.style.overflow = 'auto'; };
|
|
}, [isMenuOpen]);
|
|
|
|
useEffect(() => {
|
|
const handler = (e: MouseEvent) => {
|
|
if (langDropdownRef.current && !langDropdownRef.current.contains(e.target as Node)) {
|
|
setIsLangOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener('mousedown', handler);
|
|
return () => document.removeEventListener('mousedown', handler);
|
|
}, []);
|
|
|
|
const scrollToContact = (e: React.MouseEvent) => {
|
|
e.preventDefault();
|
|
document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' });
|
|
};
|
|
|
|
const scrollToPricing = (e: React.MouseEvent) => {
|
|
e.preventDefault();
|
|
document.getElementById('pricing')?.scrollIntoView({ behavior: 'smooth' });
|
|
};
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setStatus('loading');
|
|
try {
|
|
const response = await fetch('https://formspree.io/f/xbdaajlo', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
body: JSON.stringify(formData),
|
|
});
|
|
if (response.ok) {
|
|
setStatus('success');
|
|
setFormData({ firstName: '', lastName: '', email: '', message: '' });
|
|
} else {
|
|
setStatus('error');
|
|
}
|
|
} catch {
|
|
setStatus('error');
|
|
}
|
|
};
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
const { name, value } = e.target;
|
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
|
};
|
|
|
|
return (
|
|
<main className="bg-slate-50 text-slate-900 selection:bg-primary/30 font-display">
|
|
|
|
{/* Navbar */}
|
|
<header className={`fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-md border-b border-slate-200 transition-transform duration-300 ${navVisible ? 'translate-y-0' : '-translate-y-full'}`}>
|
|
<div className="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
|
|
<Link href={`/${lang}`} className="flex items-center gap-3 shrink-0">
|
|
<Image
|
|
src={resolveImage('{{DATA:IMAGE:IMAGE_14}}')}
|
|
alt="MyInfoMate Logo"
|
|
height={40}
|
|
width={42}
|
|
className="h-10 w-auto object-contain"
|
|
/>
|
|
<span className="text-xl font-extrabold tracking-tight text-slate-900 hidden lg:block">MyInfoMate</span>
|
|
</Link>
|
|
<nav className="hidden lg:flex items-center gap-8">
|
|
<Link href={`/${lang}#deployment-modes`} className="text-sm font-semibold text-slate-600 hover:text-primary transition-colors">{t.nav.solution}</Link>
|
|
<Link href={`/${lang}#features`} className="text-sm font-semibold text-slate-600 hover:text-primary transition-colors">{t.nav.features}</Link>
|
|
<Link href={`/${lang}#ai-assistant`} className="text-sm font-semibold text-slate-600 hover:text-primary transition-colors flex items-center gap-1">
|
|
<span className="material-symbols-outlined text-primary" style={{ fontSize: '15px' }}>auto_awesome</span>
|
|
{t.nav.ai}
|
|
</Link>
|
|
<Link href={`/${lang}#audience`} className="text-sm font-semibold text-slate-600 hover:text-primary transition-colors">{t.nav.audience}</Link>
|
|
<button onClick={scrollToPricing} className="text-sm font-semibold text-slate-600 hover:text-primary transition-colors">{t.pricing.sectionLabel}</button>
|
|
</nav>
|
|
<div className="flex items-center gap-4">
|
|
<a href="https://manager.myinfomate.be" className="hidden lg:block text-sm font-bold text-slate-900 hover:text-primary px-4 py-2 transition-colors">
|
|
{t.nav.login}
|
|
</a>
|
|
<button
|
|
onClick={scrollToContact}
|
|
className="bg-primary hover:brightness-110 text-slate-900 font-extrabold text-sm px-6 py-3 rounded-full shadow-lg shadow-primary/20 transition-all active:scale-95 hidden sm:block"
|
|
>
|
|
{t.nav.demo}
|
|
</button>
|
|
<div ref={langDropdownRef} className="relative hidden lg:block">
|
|
<button
|
|
onClick={() => setIsLangOpen(!isLangOpen)}
|
|
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-sm font-bold text-slate-500 hover:text-slate-900 hover:bg-slate-100 transition-all"
|
|
>
|
|
<span className="material-symbols-outlined text-[18px] text-primary">language</span>
|
|
<span>{lang.toUpperCase()}</span>
|
|
<span className={`material-symbols-outlined text-[16px] transition-transform duration-200 ${isLangOpen ? 'rotate-180' : ''}`}>keyboard_arrow_down</span>
|
|
</button>
|
|
{isLangOpen && (
|
|
<div className="absolute top-full right-0 mt-2 bg-white border border-slate-100 rounded-2xl shadow-xl overflow-hidden py-1.5 z-50">
|
|
{LANGUAGES.map((l) => (
|
|
<button
|
|
key={l.code}
|
|
onClick={() => { setLang(l.code); setIsLangOpen(false); }}
|
|
className={`w-full flex items-center gap-3 px-4 py-2.5 text-left transition-colors ${lang === l.code ? 'text-primary bg-primary/5' : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'}`}
|
|
>
|
|
<span className="text-sm font-black w-7 shrink-0">{l.label}</span>
|
|
<span className="text-sm text-slate-400 whitespace-nowrap">{l.name}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<button
|
|
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
|
className="lg:hidden w-10 h-10 flex items-center justify-center text-slate-600 hover:text-primary transition-colors"
|
|
>
|
|
<span className="material-symbols-outlined text-3xl">{isMenuOpen ? 'close' : 'menu'}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Mobile Menu */}
|
|
<div
|
|
className={`lg:hidden fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-[150] transition-opacity duration-300 ${isMenuOpen ? 'opacity-100 visible' : 'opacity-0 invisible pointer-events-none'}`}
|
|
onClick={() => setIsMenuOpen(false)}
|
|
/>
|
|
<div className={`lg:hidden fixed inset-y-0 right-0 w-[300px] h-screen bg-white z-[200] shadow-2xl transition-transform duration-500 ease-in-out transform ${isMenuOpen ? 'translate-x-0' : 'translate-x-full'}`}>
|
|
<div className="flex flex-col h-full bg-white">
|
|
<div className="flex justify-end p-6 border-b border-slate-50">
|
|
<button onClick={() => setIsMenuOpen(false)} className="w-10 h-10 flex items-center justify-center">
|
|
<span className="material-symbols-outlined text-3xl font-bold text-slate-900">close</span>
|
|
</button>
|
|
</div>
|
|
<nav className="flex-1 flex flex-col p-8 gap-8 overflow-y-auto">
|
|
<div className="flex flex-col gap-6">
|
|
{[
|
|
{ label: t.mobileMenu.home, href: `/${lang}` },
|
|
{ label: t.mobileMenu.solution, href: `/${lang}#deployment-modes` },
|
|
{ label: t.mobileMenu.features, href: `/${lang}#features` },
|
|
{ label: t.mobileMenu.ai, href: `/${lang}#ai-assistant` },
|
|
{ label: t.mobileMenu.audience, href: `/${lang}#audience` },
|
|
{ label: t.pricing.sectionLabel.toUpperCase(), href: '#pricing' },
|
|
{ label: t.mobileMenu.contact, href: '#contact' },
|
|
].map((item) => (
|
|
<a
|
|
key={item.label}
|
|
className="text-sm font-bold tracking-[0.2em] text-slate-900 hover:text-primary transition-colors py-3 border-b border-slate-50"
|
|
href={item.href}
|
|
onClick={() => setIsMenuOpen(false)}
|
|
>
|
|
{item.label}
|
|
</a>
|
|
))}
|
|
</div>
|
|
<div className="flex flex-col gap-4 pt-4">
|
|
<div className="flex flex-col gap-2">
|
|
{LANGUAGES.map((l) => (
|
|
<button
|
|
key={l.code}
|
|
onClick={() => { setLang(l.code); setIsMenuOpen(false); }}
|
|
className={`flex items-center gap-3 px-4 py-2.5 rounded-xl text-left transition-colors ${lang === l.code ? 'text-primary bg-primary/5 font-bold' : 'text-slate-500'}`}
|
|
>
|
|
<span className="text-sm font-black w-7">{l.label}</span>
|
|
<span className="text-sm">{l.name}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<div className="p-8 pt-0">
|
|
<button
|
|
onClick={(e) => { scrollToContact(e); setIsMenuOpen(false); }}
|
|
className="w-full py-4 bg-primary text-slate-900 font-extrabold rounded-2xl"
|
|
>
|
|
{t.nav.demo}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-20">
|
|
|
|
{/* Breadcrumb */}
|
|
<div className="max-w-7xl mx-auto px-6 pt-6">
|
|
<Link href={`/${lang}`} className="inline-flex items-center gap-1 text-sm text-slate-500 hover:text-primary transition-colors">
|
|
<span className="material-symbols-outlined text-base">arrow_back</span>
|
|
{s.nav.backLabel}
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Hero */}
|
|
<section className="py-16 lg:py-24 bg-slate-50">
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
<Reveal className="max-w-4xl mx-auto text-center">
|
|
<span className="inline-flex items-center gap-2 bg-primary/10 text-primary font-extrabold text-xs uppercase tracking-widest px-4 py-2 rounded-full mb-6">
|
|
<span className="material-symbols-outlined text-sm">museum</span>
|
|
{s.hero.badge}
|
|
</span>
|
|
<h1 className="text-4xl lg:text-6xl font-extrabold text-slate-900 leading-tight mb-6">
|
|
{s.hero.title}
|
|
</h1>
|
|
<p className="text-lg lg:text-xl text-slate-500 mb-10 max-w-2xl mx-auto leading-relaxed">
|
|
{s.hero.subtitle}
|
|
</p>
|
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
|
<button
|
|
onClick={scrollToContact}
|
|
className="px-8 py-4 bg-primary text-slate-900 font-extrabold rounded-full shadow-xl shadow-primary/30 hover:scale-105 transition-all"
|
|
>
|
|
{s.hero.cta}
|
|
</button>
|
|
<button
|
|
onClick={scrollToPricing}
|
|
className="px-8 py-4 bg-white text-slate-700 font-bold rounded-full border-2 border-slate-200 hover:border-primary hover:text-primary transition-all"
|
|
>
|
|
{s.hero.ctaSecondary}
|
|
</button>
|
|
</div>
|
|
</Reveal>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Pain Points */}
|
|
<section className="py-20 bg-white">
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
<Reveal className="text-center mb-12">
|
|
<h2 className="text-accent-orange font-extrabold uppercase tracking-widest text-sm mb-4">{s.painPoints.label}</h2>
|
|
<h3 className="text-3xl lg:text-4xl font-extrabold text-slate-900">{s.painPoints.title}</h3>
|
|
</Reveal>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
{s.painPoints.items.map((item, i) => (
|
|
<Reveal key={i} delay={i * 80}>
|
|
<div className="flex flex-col items-center text-center p-8 rounded-3xl bg-slate-50 border border-slate-100">
|
|
<div className="w-16 h-16 bg-red-50 text-red-400 rounded-2xl flex items-center justify-center mb-5">
|
|
<span className="material-symbols-outlined text-3xl">{item.icon}</span>
|
|
</div>
|
|
<h4 className="text-lg font-extrabold text-slate-900 mb-3">{item.title}</h4>
|
|
<p className="text-sm text-slate-500 leading-relaxed">{item.desc}</p>
|
|
</div>
|
|
</Reveal>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Features */}
|
|
<section className="py-20 bg-slate-50">
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
<Reveal className="text-center mb-12">
|
|
<h2 className="text-accent-orange font-extrabold uppercase tracking-widest text-sm mb-4">{s.features.label}</h2>
|
|
<h3 className="text-3xl lg:text-4xl font-extrabold text-slate-900 mb-4">{s.features.title}</h3>
|
|
<p className="text-slate-500 max-w-xl mx-auto">{s.features.desc}</p>
|
|
</Reveal>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
|
{s.features.items.map((item, i) => (
|
|
<Reveal key={i} delay={i * 60}>
|
|
<div className="flex flex-col bg-white rounded-3xl border border-slate-100 p-8 hover:shadow-lg hover:shadow-primary/5 hover:border-primary/20 transition-all h-full">
|
|
<div className="w-14 h-14 bg-primary/10 text-primary rounded-2xl flex items-center justify-center mb-5 shrink-0">
|
|
<span className="material-symbols-outlined text-2xl">{item.icon}</span>
|
|
</div>
|
|
<h4 className="text-lg font-extrabold text-slate-900 mb-3">{item.title}</h4>
|
|
<p className="text-sm text-slate-500 leading-relaxed flex-1 mb-4">{item.desc}</p>
|
|
<div className="pt-4 border-t border-slate-100">
|
|
<p className="text-xs text-primary font-bold">
|
|
<span className="text-slate-400 font-normal">{s.features.valueLabel} </span>
|
|
{item.value}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</Reveal>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Comparison */}
|
|
<section className="py-20 bg-white">
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
<Reveal className="text-center mb-12">
|
|
<h2 className="text-accent-orange font-extrabold uppercase tracking-widest text-sm mb-4">{s.comparison.label}</h2>
|
|
<h3 className="text-3xl lg:text-4xl font-extrabold text-slate-900">{s.comparison.title}</h3>
|
|
</Reveal>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
{s.comparison.items.map((item, i) => (
|
|
<Reveal key={i} delay={i * 80}>
|
|
<div className="bg-slate-50 rounded-3xl border border-slate-200 p-8 h-full">
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<div className="w-10 h-10 bg-slate-200 rounded-xl flex items-center justify-center">
|
|
<span className="material-symbols-outlined text-slate-500 text-lg">compare</span>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-slate-400 uppercase tracking-widest font-bold">vs</p>
|
|
<p className="font-extrabold text-slate-900">{item.competitor}</p>
|
|
</div>
|
|
</div>
|
|
<ul className="space-y-3">
|
|
{item.advantages.map((adv, j) => (
|
|
<li key={j} className="flex items-start gap-3 text-sm text-slate-700">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0 mt-0.5">check_circle</span>
|
|
{adv}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</Reveal>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Pricing */}
|
|
<section className="py-20 bg-slate-50" id="pricing">
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
<Reveal className="text-center mb-12">
|
|
<h2 className="text-accent-orange font-extrabold uppercase tracking-widest text-sm mb-4">{t.pricing.sectionLabel}</h2>
|
|
<h3 className="text-3xl lg:text-4xl font-extrabold text-slate-900 mb-4">{t.pricing.sectionTitle}</h3>
|
|
<p className="text-slate-500 max-w-xl mx-auto">{t.pricing.sectionDesc}</p>
|
|
</Reveal>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 items-stretch pt-6">
|
|
|
|
{/* Essentiel */}
|
|
<Reveal delay={0}>
|
|
<div className="flex flex-col h-full bg-white rounded-3xl border border-slate-200 p-8 shadow-sm hover:shadow-md transition-shadow relative">
|
|
<div className="absolute -top-4 left-1/2 -translate-x-1/2">
|
|
<span className="bg-slate-800 text-white text-xs font-extrabold px-4 py-1.5 rounded-full uppercase tracking-wider whitespace-nowrap">{t.pricing.comingSoon}</span>
|
|
</div>
|
|
<div className="mb-6">
|
|
<p className="text-sm font-bold text-slate-400 uppercase tracking-widest mb-2">Essentiel</p>
|
|
<div className="flex items-end gap-1">
|
|
<span className="text-4xl font-extrabold text-slate-900">€39</span>
|
|
<span className="text-slate-400 mb-1">{t.pricing.perMonth}</span>
|
|
</div>
|
|
<p className="text-xs text-slate-400 mt-1">{t.pricing.htva} · {t.pricing.noCommitment}</p>
|
|
</div>
|
|
<ul className="space-y-3 flex-1 mb-8">
|
|
{[t.pricing.features.webAndKiosk, t.pricing.features.backoffice, t.pricing.features.sections].map((f) => (
|
|
<li key={f} className="flex items-center gap-3 text-sm text-slate-700">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
{f}
|
|
</li>
|
|
))}
|
|
<li className="flex items-center gap-3 text-sm text-slate-700">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
1 GB {t.pricing.features.storage}
|
|
</li>
|
|
<li className="flex items-center gap-3 text-sm text-slate-700">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
{t.pricing.features.stats} — {t.pricing.features.statsBasicLabel}
|
|
</li>
|
|
{[t.pricing.features.nativeApp, t.pricing.features.offlineBeacons, t.pricing.features.pushNotif, t.pricing.features.ai, t.pricing.features.autoTranslation].map((f) => (
|
|
<li key={f} className="flex items-center gap-3 text-sm text-slate-400">
|
|
<span className="material-symbols-outlined text-slate-300 text-base shrink-0">cancel</span>
|
|
{f}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<button onClick={scrollToContact} className="w-full py-3 rounded-2xl border-2 border-slate-200 text-slate-700 font-bold text-sm hover:border-primary hover:text-primary transition-all">
|
|
{t.pricing.ctaStart}
|
|
</button>
|
|
</div>
|
|
</Reveal>
|
|
|
|
{/* Pro */}
|
|
<Reveal delay={60}>
|
|
<div className="flex flex-col h-full bg-white rounded-3xl border-2 border-primary p-8 shadow-xl shadow-primary/10 relative">
|
|
<div className="absolute -top-4 left-1/2 -translate-x-1/2">
|
|
<span className="bg-primary text-slate-900 text-xs font-extrabold px-4 py-1.5 rounded-full uppercase tracking-wider">{t.pricing.recommended}</span>
|
|
</div>
|
|
<div className="mb-6">
|
|
<p className="text-sm font-bold text-slate-400 uppercase tracking-widest mb-2">Pro</p>
|
|
<div className="flex items-end gap-1">
|
|
<span className="text-4xl font-extrabold text-slate-900">€99</span>
|
|
<span className="text-slate-400 mb-1">{t.pricing.perMonth}</span>
|
|
</div>
|
|
<p className="text-xs text-slate-400 mt-1">{t.pricing.htva}</p>
|
|
<p className="text-xs text-slate-400 mt-1">{t.pricing.setupFeeNote}</p>
|
|
</div>
|
|
<ul className="space-y-3 flex-1 mb-8">
|
|
{[t.pricing.features.webAndKiosk, t.pricing.features.nativeApp, t.pricing.features.backoffice, t.pricing.features.sections].map((f) => (
|
|
<li key={f} className="flex items-center gap-3 text-sm text-slate-700">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
{f}
|
|
</li>
|
|
))}
|
|
<li className="flex items-center gap-3 text-sm text-slate-700">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
15 GB {t.pricing.features.storage}
|
|
</li>
|
|
{[t.pricing.features.offlineBeacons, t.pricing.features.pushNotif].map((f) => (
|
|
<li key={f} className="flex items-center gap-3 text-sm text-slate-700">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
{f}
|
|
</li>
|
|
))}
|
|
<li className="flex items-center gap-3 text-sm text-slate-700">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
{t.pricing.features.stats} — {t.pricing.features.statsBasicLabel}
|
|
</li>
|
|
{[t.pricing.features.statsAdvancedFeature, t.pricing.features.ai, t.pricing.features.autoTranslation].map((f) => (
|
|
<li key={f} className="flex items-center gap-3 text-sm text-slate-400">
|
|
<span className="material-symbols-outlined text-slate-300 text-base shrink-0">cancel</span>
|
|
{f}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<button onClick={scrollToContact} className="w-full py-3 rounded-2xl bg-primary text-slate-900 font-extrabold text-sm hover:brightness-110 transition-all shadow-lg shadow-primary/20">
|
|
{t.pricing.ctaStart}
|
|
</button>
|
|
</div>
|
|
</Reveal>
|
|
|
|
{/* Bundle */}
|
|
<Reveal delay={120}>
|
|
<div className="flex flex-col h-full bg-gradient-to-br from-slate-900 to-slate-800 rounded-3xl p-8 shadow-xl">
|
|
<div className="mb-6">
|
|
<p className="text-sm font-bold text-slate-400 uppercase tracking-widest mb-2">Bundle</p>
|
|
<div className="flex items-end gap-1">
|
|
<span className="text-4xl font-extrabold text-white">€179</span>
|
|
<span className="text-slate-400 mb-1">{t.pricing.perMonth}</span>
|
|
</div>
|
|
<p className="text-xs text-slate-400 mt-1">{t.pricing.htva}</p>
|
|
<p className="text-xs text-slate-500 mt-1">{t.pricing.setupFeeNote}</p>
|
|
</div>
|
|
<ul className="space-y-3 flex-1 mb-8">
|
|
{[t.pricing.features.webAndKiosk, t.pricing.features.nativeApp, t.pricing.features.backoffice, t.pricing.features.sections].map((f) => (
|
|
<li key={f} className="flex items-center gap-3 text-sm text-slate-300">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
{f}
|
|
</li>
|
|
))}
|
|
<li className="flex items-center gap-3 text-sm text-slate-300">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
50 GB {t.pricing.features.storage}
|
|
</li>
|
|
{[t.pricing.features.offlineBeacons, t.pricing.features.pushNotif].map((f) => (
|
|
<li key={f} className="flex items-center gap-3 text-sm text-slate-300">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
{f}
|
|
</li>
|
|
))}
|
|
<li className="flex items-center gap-3 text-sm text-slate-300">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
{t.pricing.features.statsAdvancedFeature}
|
|
</li>
|
|
<li className="flex items-center gap-3 text-sm text-slate-300">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
{t.pricing.features.ai} — 2 000 {t.pricing.features.reqPerMonth}
|
|
</li>
|
|
<li className="flex items-center gap-3 text-sm text-slate-300">
|
|
<span className="material-symbols-outlined text-primary text-base shrink-0">check_circle</span>
|
|
{t.pricing.features.autoTranslation}
|
|
</li>
|
|
</ul>
|
|
<button onClick={scrollToContact} className="w-full py-3 rounded-2xl bg-white/10 text-white font-bold text-sm hover:bg-white/20 transition-all border border-white/10">
|
|
{t.pricing.ctaContact}
|
|
</button>
|
|
</div>
|
|
</Reveal>
|
|
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* FAQ */}
|
|
<section className="py-20 bg-white">
|
|
<div className="max-w-3xl mx-auto px-6">
|
|
<Reveal className="text-center mb-12">
|
|
<h2 className="text-accent-orange font-extrabold uppercase tracking-widest text-sm mb-4">{s.faq.label}</h2>
|
|
<h3 className="text-3xl lg:text-4xl font-extrabold text-slate-900">{s.faq.title}</h3>
|
|
</Reveal>
|
|
<div className="space-y-3">
|
|
{s.faq.items.map((item, i) => (
|
|
<Reveal key={i} delay={i * 40}>
|
|
<div className="bg-slate-50 rounded-2xl border border-slate-100 overflow-hidden">
|
|
<button
|
|
onClick={() => setOpenFaq(openFaq === i ? null : i)}
|
|
className="w-full flex items-center justify-between p-6 text-left hover:bg-slate-100 transition-colors"
|
|
>
|
|
<span className="font-bold text-slate-900 pr-4 text-sm lg:text-base">{item.question}</span>
|
|
<span className={`material-symbols-outlined text-primary shrink-0 transition-transform duration-300 ${openFaq === i ? 'rotate-180' : ''}`}>
|
|
expand_more
|
|
</span>
|
|
</button>
|
|
<div className={`overflow-hidden transition-all duration-300 ${openFaq === i ? 'max-h-96' : 'max-h-0'}`}>
|
|
<p className="px-6 pb-6 text-sm text-slate-600 leading-relaxed">{item.answer}</p>
|
|
</div>
|
|
</div>
|
|
</Reveal>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* CTA */}
|
|
<section className="py-20 bg-slate-50">
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
<Reveal>
|
|
<div className="bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 rounded-[3rem] p-12 lg:p-24 text-center relative overflow-hidden">
|
|
<div className="absolute top-0 left-0 w-full h-full opacity-10 pointer-events-none">
|
|
<div className="absolute top-10 left-10 w-40 h-40 bg-primary rounded-full blur-3xl"></div>
|
|
<div className="absolute bottom-10 right-10 w-60 h-60 bg-accent-violet rounded-full blur-3xl"></div>
|
|
</div>
|
|
<h2 className="text-3xl lg:text-5xl font-extrabold text-white mb-6 relative z-10">{s.cta.title}</h2>
|
|
<p className="text-slate-400 text-lg mb-12 max-w-2xl mx-auto relative z-10">{s.cta.subtitle}</p>
|
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-6 relative z-10">
|
|
<button
|
|
onClick={scrollToContact}
|
|
className="w-full sm:w-auto px-10 py-5 bg-primary text-slate-900 font-extrabold rounded-full shadow-2xl shadow-primary/40 hover:scale-105 transition-all"
|
|
>
|
|
{s.cta.button1}
|
|
</button>
|
|
<a
|
|
href="mailto:contact@unov.be"
|
|
className="w-full sm:w-auto px-10 py-5 bg-white/10 text-white font-bold rounded-full hover:bg-white/20 transition-all border border-white/10 text-center"
|
|
>
|
|
{s.cta.button2}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</Reveal>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Contact */}
|
|
<section className="py-24 bg-white relative overflow-hidden" id="contact">
|
|
<div className="absolute top-0 right-0 -z-10 w-64 h-64 bg-primary/5 rounded-full blur-3xl"></div>
|
|
<div className="absolute bottom-0 left-0 -z-10 w-96 h-96 bg-accent-violet/5 rounded-full blur-3xl"></div>
|
|
<div className="max-w-4xl mx-auto px-6">
|
|
<div className="bg-white rounded-[3rem] shadow-2xl border border-slate-100 p-10 lg:p-16 relative">
|
|
<div className="text-center mb-12">
|
|
<h2 className="text-accent-orange font-extrabold uppercase tracking-widest text-sm mb-4">{t.contact.sectionLabel}</h2>
|
|
<h3 className="text-3xl lg:text-4xl font-extrabold text-slate-900 mb-4">{t.contact.title}</h3>
|
|
<p className="text-slate-600">{t.contact.subtitle}</p>
|
|
</div>
|
|
{status === 'success' ? (
|
|
<div className="text-center py-20">
|
|
<div className="w-20 h-20 bg-primary/20 text-primary rounded-full flex items-center justify-center mx-auto mb-6">
|
|
<span className="material-symbols-outlined text-5xl">task_alt</span>
|
|
</div>
|
|
<h3 className="text-3xl font-extrabold text-slate-900 mb-4">{t.contact.successTitle}</h3>
|
|
<p className="text-slate-600 mb-8">{t.contact.successDesc}</p>
|
|
<button onClick={() => setStatus('idle')} className="text-primary font-bold hover:underline">
|
|
{t.contact.successButton}
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
{status === 'error' && (
|
|
<div className="p-4 bg-red-50 text-red-600 rounded-xl text-sm font-medium border border-red-100 flex items-center gap-2">
|
|
<span className="material-symbols-outlined text-sm">error</span>
|
|
{t.contact.errorMsg}
|
|
</div>
|
|
)}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-bold text-slate-700 ml-1">{t.contact.firstNameLabel}</label>
|
|
<input type="text" name="firstName" required value={formData.firstName} onChange={handleChange}
|
|
className="w-full px-6 py-4 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all"
|
|
placeholder={t.contact.firstNamePlaceholder} />
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-bold text-slate-700 ml-1">{t.contact.lastNameLabel}</label>
|
|
<input type="text" name="lastName" required value={formData.lastName} onChange={handleChange}
|
|
className="w-full px-6 py-4 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all"
|
|
placeholder={t.contact.lastNamePlaceholder} />
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-bold text-slate-700 ml-1">{t.contact.emailLabel}</label>
|
|
<input type="email" name="email" required value={formData.email} onChange={handleChange}
|
|
className="w-full px-6 py-4 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all"
|
|
placeholder={t.contact.emailPlaceholder} />
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-bold text-slate-700 ml-1">{t.contact.messageLabel}</label>
|
|
<textarea name="message" rows={4} value={formData.message} onChange={handleChange}
|
|
className="w-full px-6 py-4 bg-slate-50 border border-slate-200 rounded-2xl focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all resize-none"
|
|
placeholder={t.contact.messagePlaceholder}></textarea>
|
|
</div>
|
|
<button type="submit" disabled={status === 'loading'}
|
|
className={`w-full py-5 bg-slate-900 text-white font-extrabold rounded-2xl shadow-xl hover:bg-slate-800 transition-all flex items-center justify-center gap-3 group ${status === 'loading' ? 'opacity-70 cursor-not-allowed' : ''}`}>
|
|
{status === 'loading' ? (
|
|
<span className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin"></span>
|
|
) : (
|
|
<>
|
|
{t.contact.submitButton}
|
|
<span className="material-symbols-outlined group-hover:translate-x-1 transition-transform">send</span>
|
|
</>
|
|
)}
|
|
</button>
|
|
</form>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<footer className="bg-slate-50 pt-20 pb-10 border-t border-slate-200">
|
|
<div className="max-w-7xl mx-auto px-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-16">
|
|
<div className="col-span-1 lg:col-span-1">
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<Image
|
|
src={resolveImage('{{DATA:IMAGE:IMAGE_14}}')}
|
|
alt="MyInfoMate Logo"
|
|
height={32}
|
|
width={33}
|
|
className="h-8 w-auto object-contain"
|
|
/>
|
|
<span className="text-lg font-extrabold tracking-tight text-slate-900">MyInfoMate</span>
|
|
</div>
|
|
<p className="text-slate-500 text-sm leading-relaxed mb-6">{t.footer.desc}</p>
|
|
<div className="flex gap-4">
|
|
<a className="w-10 h-10 rounded-full bg-slate-200 flex items-center justify-center text-slate-600 hover:bg-primary hover:text-slate-900 transition-all" href="#">
|
|
<span className="material-symbols-outlined">public</span>
|
|
</a>
|
|
<a className="w-10 h-10 rounded-full bg-slate-200 flex items-center justify-center text-slate-600 hover:bg-primary hover:text-slate-900 transition-all" href="mailto:contact@unov.be">
|
|
<span className="material-symbols-outlined">alternate_email</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div></div>
|
|
<div></div>
|
|
<div>
|
|
<h4 className="font-bold text-slate-900 mb-6 uppercase text-xs tracking-widest">{t.footer.contactTitle}</h4>
|
|
<ul className="space-y-4">
|
|
<li className="flex items-start gap-3 text-sm text-slate-500">
|
|
<span className="material-symbols-outlined text-primary text-sm">location_on</span>
|
|
UNOV, 5020 Namur
|
|
</li>
|
|
<li className="flex items-center gap-3 text-sm text-slate-500">
|
|
<span className="material-symbols-outlined text-primary text-sm">mail</span>
|
|
contact@unov.be
|
|
</li>
|
|
<li className="flex items-center gap-3 text-sm text-slate-500">
|
|
<span className="material-symbols-outlined text-primary text-sm">call</span>
|
|
+32 (0)498 07 95 35
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div className="pt-8 border-t border-slate-200 flex flex-col md:flex-row justify-between items-center gap-4">
|
|
<p className="text-xs text-slate-400">{t.footer.copyright}</p>
|
|
<div className="flex gap-8">
|
|
<a className="text-xs text-slate-400 hover:text-slate-600" href="/mentions-legales">{t.footer.legal}</a>
|
|
<a className="text-xs text-slate-400 hover:text-slate-600" href="/confidentialite">{t.footer.privacy}</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
</main>
|
|
);
|
|
}
|