maj designe

This commit is contained in:
lfirmin
2026-02-28 20:11:30 +01:00
parent fc9f384b47
commit 2f78c8d6f7
16 changed files with 330 additions and 173 deletions
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 2.0 MiB

+34
View File
@@ -0,0 +1,34 @@
# ─── Désactiver le listing des répertoires ──────────────────────────────────
Options -Indexes
# ─── Bloquer les fichiers sensibles ─────────────────────────────────────────
<FilesMatch "\.(zip|tar|gz|rar|7z|sql|log|env|bak|old|orig|save|swp|tmp|sh|py|rb|pl)$">
Order allow,deny
Deny from all
</FilesMatch>
# ─── Bloquer l'accès direct aux fichiers cachés (.htaccess, .env, etc.) ─────
<FilesMatch "^\.">
Order allow,deny
Deny from all
</FilesMatch>
# ─── Headers de sécurité ─────────────────────────────────────────────────────
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
# ─── SPA routing (React Router / Vite) ──────────────────────────────────────
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# Ne pas réécrire les fichiers et dossiers existants
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Tout le reste → index.html
RewriteRule ^ index.html [L]
</IfModule>
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

+13 -5
View File
@@ -1,19 +1,27 @@
import { useState, useEffect } from 'react'
import Navbar from './components/Navbar' import Navbar from './components/Navbar'
import Hero from './components/Hero' import Hero from './components/Hero'
import ComparisonSection from './components/ComparisonSection' import ComparisonSection from './components/ComparisonSection'
import Testimonials from './components/Testimonials'
import GuideSection from './components/GuideSection' import GuideSection from './components/GuideSection'
import Footer from './components/Footer' import Footer from './components/Footer'
export default function App() { export default function App() {
const [bgSrc, setBgSrc] = useState(null)
useEffect(() => {
fetch('/config.json')
.then(r => r.json())
.then(data => setBgSrc(data.heroBg ?? null))
.catch(() => {})
}, [])
return ( return (
<div className="min-h-screen bg-white"> <div className="min-h-screen bg-[#19392e]">
<Navbar /> <Navbar />
<main> <main>
<Hero /> <Hero bgSrc={bgSrc} />
<ComparisonSection /> <ComparisonSection />
<Testimonials /> <GuideSection bgSrc={bgSrc} />
<GuideSection />
</main> </main>
<Footer /> <Footer />
</div> </div>
+50
View File
@@ -0,0 +1,50 @@
import { useEffect } from 'react'
import { motion, useAnimation } from 'framer-motion'
const LETTERS_1 = 'Assurez'.split('')
const LETTERS_2 = 'Moi'.split('')
export default function AnimatedBrandName({ className = '', lightText = false }) {
const controls = useAnimation()
useEffect(() => {
const play = () =>
controls.start(i => ({
y: [0, -9, 0],
transition: {
delay: i * 0.055,
duration: 0.38,
ease: 'easeInOut',
},
}))
play()
const interval = setInterval(play, 5000)
return () => clearInterval(interval)
}, [controls])
return (
<span className={`font-extrabold text-xl tracking-tight inline-flex ${className}`}>
{LETTERS_1.map((letter, i) => (
<motion.span
key={i}
custom={i}
animate={controls}
className={`inline-block ${lightText ? 'text-[#fffef6]' : 'text-slate-900'}`}
>
{letter}
</motion.span>
))}
{LETTERS_2.map((letter, i) => (
<motion.span
key={`moi-${i}`}
custom={LETTERS_1.length + i}
animate={controls}
className="inline-block text-[#9CCC8A]"
>
{letter}
</motion.span>
))}
</span>
)
}
+12 -8
View File
@@ -98,11 +98,17 @@ export default function ComparisonSection() {
const { mutuelles, loading, error } = useMutuelles() const { mutuelles, loading, error } = useMutuelles()
return ( return (
<section id="classement" className="pt-10 pb-20 bg-white"> <section id="classement" className="relative isolate pt-10 pb-20 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Header */} {/* Header */}
<div className="text-center mb-14"> <motion.div
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}
transition={{ duration: 0.5 }}
className="text-center mb-14"
>
<div className="inline-flex items-center gap-2 bg-amber-50 border border-amber-200 text-amber-700 text-xs font-bold px-3 py-1.5 rounded-full mb-5"> <div className="inline-flex items-center gap-2 bg-amber-50 border border-amber-200 text-amber-700 text-xs font-bold px-3 py-1.5 rounded-full mb-5">
<FiAward size={13} /> <FiAward size={13} />
Classement officiel 2026 Classement officiel 2026
@@ -114,7 +120,7 @@ export default function ComparisonSection() {
Classement mis à jour chaque mois basé sur les garanties, les tarifs Classement mis à jour chaque mois basé sur les garanties, les tarifs
et les avis clients de plus de 186 mutuelles analysées. et les avis clients de plus de 186 mutuelles analysées.
</p> </p>
</div> </motion.div>
{/* Erreur */} {/* Erreur */}
{error && ( {error && (
@@ -134,7 +140,7 @@ export default function ComparisonSection() {
key={m.rang} key={m.rang}
initial={{ opacity: 0, y: 24 }} initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: false }}
transition={{ delay: i * 0.1, duration: 0.45 }} transition={{ delay: i * 0.1, duration: 0.45 }}
> >
<TopCard mutuelle={m} index={i} /> <TopCard mutuelle={m} index={i} />
@@ -143,12 +149,10 @@ export default function ComparisonSection() {
</div> </div>
{/* Séparateur */} {/* Séparateur */}
<div className="flex items-center gap-4 mb-8"> <div className="flex items-center justify-center mb-8">
<div className="flex-1 h-px bg-slate-100" />
<span className="text-xs font-bold text-slate-400 uppercase tracking-widest"> <span className="text-xs font-bold text-slate-400 uppercase tracking-widest">
Classement complet Classement complet
</span> </span>
<div className="flex-1 h-px bg-slate-100" />
</div> </div>
{/* Liste complète */} {/* Liste complète */}
@@ -158,7 +162,7 @@ export default function ComparisonSection() {
key={m.rang} key={m.rang}
initial={{ opacity: 0, x: -16 }} initial={{ opacity: 0, x: -16 }}
whileInView={{ opacity: 1, x: 0 }} whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }} viewport={{ once: false }}
transition={{ delay: i * 0.06, duration: 0.4 }} transition={{ delay: i * 0.06, duration: 0.4 }}
> >
<MutuelleCard mutuelle={m} /> <MutuelleCard mutuelle={m} />
+54 -54
View File
@@ -1,11 +1,6 @@
import { import { motion } from 'framer-motion'
FiFacebook, import AnimatedBrandName from './AnimatedBrandName'
FiTwitter, import { FiArrowUpRight } from 'react-icons/fi'
FiInstagram,
FiLinkedin,
FiYoutube,
FiArrowUpRight,
} from 'react-icons/fi'
const colResources = ['FAQ', 'Guides', 'Lexique', 'Documents'] const colResources = ['FAQ', 'Guides', 'Lexique', 'Documents']
@@ -25,58 +20,60 @@ const colLegal = [
"Conditions générales d'utilisation", "Conditions générales d'utilisation",
] ]
const socials = [
{ Icon: FiFacebook, label: 'Facebook' },
{ Icon: FiTwitter, label: 'Twitter / X' },
{ Icon: FiInstagram, label: 'Instagram' },
{ Icon: FiLinkedin, label: 'LinkedIn' },
{ Icon: FiYoutube, label: 'YouTube' },
]
export default function Footer() { export default function Footer() {
return ( return (
<footer className="bg-[#F2F8EE] text-slate-500 border-t border-[#9CCC8A]/20"> <footer className="bg-[#19392e]">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Main grid */} {/* Main grid */}
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-10 py-16"> <div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-10 py-16">
{/* Brand */} {/* Brand */}
<div className="sm:col-span-2 lg:col-span-1"> <motion.div
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}
transition={{ duration: 0.45, delay: 0 }}
className="sm:col-span-2 lg:col-span-1"
>
<div className="flex items-center gap-2.5 mb-5"> <div className="flex items-center gap-2.5 mb-5">
<img <img
src="/img/ico.webp" src="/img/ico.webp"
alt="AssurezMoi" alt="AssurezMoi"
className="w-9 h-9 rounded-xl object-contain" className="w-9 h-9 rounded-xl object-contain"
/> />
<span className="font-extrabold text-xl text-slate-900 tracking-tight"> <AnimatedBrandName lightText />
Assurez<span className="text-[#9CCC8A]">Moi</span>
</span>
</div> </div>
<address className="not-italic text-sm text-slate-500 leading-relaxed mb-4"> <address className="not-italic text-sm text-[#fffef6]/60 leading-relaxed mb-4">
Votre comparateur d'assurances<br /> Votre comparateur d'assurances<br />
1 Rue john doe<br /> 1 Rue john doe<br />
59100 Roubaix, France 59100 Roubaix, France
</address> </address>
<a <a
href="mailto:[email protected]" href="mailto:[email protected]"
className="text-sm text-slate-500 hover:text-[#9CCC8A] transition-colors" className="text-sm text-[#fffef6]/60 hover:text-[#9CCC8A] transition-colors"
> >
contact@assurezmoi.com contact@assurezmoi.com
</a> </a>
<div className="mt-2"> <div className="mt-2">
<a <a
href="tel:+33180891030" href="tel:+33180891030"
className="text-sm text-slate-500 hover:text-[#9CCC8A] transition-colors" className="text-sm text-[#fffef6]/60 hover:text-[#9CCC8A] transition-colors"
> >
01 02 03 04 05 01 02 03 04 05
</a> </a>
</div> </div>
</div> </motion.div>
{/* Resources */} {/* Resources */}
<div> <motion.div
<h4 className="text-slate-700 font-bold text-sm uppercase tracking-widest mb-5"> initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}
transition={{ duration: 0.45, delay: 0.1 }}
>
<h4 className="text-[#fffef6] font-bold text-sm uppercase tracking-widest mb-5">
Ressources Ressources
</h4> </h4>
<ul className="space-y-3"> <ul className="space-y-3">
@@ -84,7 +81,7 @@ export default function Footer() {
<li key={r}> <li key={r}>
<a <a
href="#" href="#"
className="text-sm text-slate-500 hover:text-slate-900 transition-colors flex items-center gap-1 group" className="text-sm text-[#fffef6]/60 hover:text-[#9CCC8A] transition-colors flex items-center gap-1 group"
> >
{r} {r}
<FiArrowUpRight size={11} className="opacity-0 group-hover:opacity-100 transition-opacity" /> <FiArrowUpRight size={11} className="opacity-0 group-hover:opacity-100 transition-opacity" />
@@ -92,11 +89,16 @@ export default function Footer() {
</li> </li>
))} ))}
</ul> </ul>
</div> </motion.div>
{/* Discover */} {/* Discover */}
<div> <motion.div
<h4 className="text-slate-700 font-bold text-sm uppercase tracking-widest mb-5"> initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}
transition={{ duration: 0.45, delay: 0.2 }}
>
<h4 className="text-[#fffef6] font-bold text-sm uppercase tracking-widest mb-5">
Nous découvrir Nous découvrir
</h4> </h4>
<ul className="space-y-3"> <ul className="space-y-3">
@@ -104,7 +106,7 @@ export default function Footer() {
<li key={d}> <li key={d}>
<a <a
href="#" href="#"
className="text-sm text-slate-500 hover:text-slate-900 transition-colors flex items-center gap-1 group" className="text-sm text-[#fffef6]/60 hover:text-[#9CCC8A] transition-colors flex items-center gap-1 group"
> >
{d} {d}
<FiArrowUpRight size={11} className="opacity-0 group-hover:opacity-100 transition-opacity" /> <FiArrowUpRight size={11} className="opacity-0 group-hover:opacity-100 transition-opacity" />
@@ -112,46 +114,44 @@ export default function Footer() {
</li> </li>
))} ))}
</ul> </ul>
</div> </motion.div>
{/* Legal */} {/* Legal */}
<div> <motion.div
<h4 className="text-slate-700 font-bold text-sm uppercase tracking-widest mb-5"> initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}
transition={{ duration: 0.45, delay: 0.3 }}
>
<h4 className="text-[#fffef6] font-bold text-sm uppercase tracking-widest mb-5">
Informations légales Informations légales
</h4> </h4>
<ul className="space-y-3"> <ul className="space-y-3">
{colLegal.map((l) => ( {colLegal.map((l) => (
<li key={l}> <li key={l}>
<a href="#" className="text-sm text-slate-500 hover:text-slate-900 transition-colors"> <a href="#" className="text-sm text-[#fffef6]/60 hover:text-[#9CCC8A] transition-colors">
{l} {l}
</a> </a>
</li> </li>
))} ))}
</ul> </ul>
</div> </motion.div>
</div> </div>
{/* Bottom bar */} {/* Bottom bar */}
<div className="border-t border-[#9CCC8A]/20 py-6 flex flex-col sm:flex-row items-center justify-between gap-4"> <motion.div
<p className="text-xs text-slate-400 text-center sm:text-left"> initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}
transition={{ duration: 0.4, delay: 0.1 }}
className="border-t border-white/10 py-6 flex items-center justify-center"
>
<p className="text-xs text-[#fffef6]/40 text-center">
© 2026 AssurezMoi Tous droits réservés. © 2026 AssurezMoi Tous droits réservés.
<span className="mx-2 text-[#9CCC8A]/50">·</span> <span className="mx-2 text-[#9CCC8A]/50">·</span>
Comparateur de mutuelles santé & assurances Comparateur de mutuelles santé & assurances
</p> </p>
</motion.div>
<div className="flex items-center gap-2">
{socials.map(({ Icon, label }) => (
<a
key={label}
href="#"
aria-label={label}
className="w-8 h-8 rounded-lg bg-[#9CCC8A]/15 hover:bg-[#9CCC8A] text-slate-500 hover:text-white flex items-center justify-center transition-all hover:-translate-y-0.5"
>
<Icon size={14} />
</a>
))}
</div>
</div>
</div> </div>
</footer> </footer>
) )
+63 -25
View File
@@ -7,24 +7,24 @@ function FaqItem({ faq, index }) {
const [open, setOpen] = useState(index === 0) const [open, setOpen] = useState(index === 0)
return ( return (
<div className="border border-slate-100 rounded-2xl overflow-hidden"> <div className="border border-white/10 rounded-2xl overflow-hidden bg-[#19392e]/80 backdrop-blur-sm">
<button <button
onClick={() => setOpen((v) => !v)} onClick={() => setOpen((v) => !v)}
className="w-full flex items-start gap-4 px-6 py-5 text-left hover:bg-slate-50 transition-colors" className="w-full flex items-start gap-4 px-6 py-5 text-left hover:bg-white/5 transition-colors"
> >
<span <span
className="flex-shrink-0 w-7 h-7 rounded-full flex items-center justify-center text-xs font-extrabold mt-0.5 text-white" className="flex-shrink-0 w-7 h-7 rounded-full flex items-center justify-center text-xs font-extrabold mt-0.5 text-white"
style={{ backgroundColor: open ? '#9CCC8A' : '#CBD5E1' }} style={{ backgroundColor: open ? '#9CCC8A' : 'rgba(255,254,246,0.2)' }}
> >
{index + 1} {index + 1}
</span> </span>
<span className="flex-1 font-bold text-slate-900 leading-snug">{faq.question}</span> <span className="flex-1 font-bold text-[#fffef6] leading-snug">{faq.question}</span>
<motion.div <motion.div
animate={{ rotate: open ? 180 : 0 }} animate={{ rotate: open ? 180 : 0 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="flex-shrink-0 mt-0.5" className="flex-shrink-0 mt-0.5"
> >
<FiChevronDown className={`text-lg transition-colors ${open ? 'text-[#9CCC8A]' : 'text-slate-400'}`} /> <FiChevronDown className={`text-lg transition-colors ${open ? 'text-[#9CCC8A]' : 'text-[#fffef6]/40'}`} />
</motion.div> </motion.div>
</button> </button>
@@ -36,9 +36,9 @@ function FaqItem({ faq, index }) {
exit={{ height: 0, opacity: 0 }} exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25 }} transition={{ duration: 0.25 }}
> >
<div className="px-6 pb-6 space-y-3 border-t border-slate-100 pt-4"> <div className="px-6 pb-6 space-y-3 border-t border-white/10 pt-4">
{faq.content.map((para, i) => ( {faq.content.map((para, i) => (
<p key={i} className="text-sm text-slate-500 leading-relaxed"> <p key={i} className="text-sm text-[#fffef6]/60 leading-relaxed">
{para} {para}
</p> </p>
))} ))}
@@ -50,19 +50,45 @@ function FaqItem({ faq, index }) {
) )
} }
export default function GuideSection() { export default function GuideSection({ bgSrc }) {
return ( return (
<section className="py-20 bg-white"> <section
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> className="relative py-0 bg-cover bg-center bg-fixed"
style={bgSrc ? { backgroundImage: `url(${bgSrc})` } : { backgroundColor: '#19392e' }}
>
{/* Overlay */}
<div className="pointer-events-none absolute inset-0 bg-[#19392e]/70 backdrop-blur-[2px]" />
<div className="text-center mb-14"> {/* Wave ← ComparisonSection */}
<h2 className="text-4xl font-extrabold text-slate-900 tracking-tight mb-4"> <div className="absolute top-0 left-0 w-full overflow-hidden leading-[0] pointer-events-none">
<svg className="block w-full h-20 fill-white" viewBox="0 0 1200 120" preserveAspectRatio="none">
<path d="M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113-14.29,1200,52.47V0Z" />
</svg>
</div>
{/* Wave → Footer */}
<div className="absolute bottom-0 left-0 w-full overflow-hidden leading-[0] pointer-events-none">
<svg className="block w-full h-20" viewBox="0 0 1200 120" preserveAspectRatio="none" style={{ fill: '#19392e', transform: 'scaleY(-1)' }}>
<path d="M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113-14.29,1200,52.47V0Z" />
</svg>
</div>
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-48 pb-48">
<motion.div
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}z
transition={{ duration: 0.5 }}
className="text-center mb-14"
>
<h2 className="text-4xl font-extrabold text-[#fffef6] tracking-tight mb-4">
Tout savoir sur la mutuelle santé Tout savoir sur la mutuelle santé
</h2> </h2>
<p className="text-slate-500 max-w-xl mx-auto"> <p className="text-[#fffef6]/60 max-w-xl mx-auto">
Nos guides répondent à toutes vos questions pour faire le bon choix. Nos guides répondent à toutes vos questions pour faire le bon choix.
</p> </p>
</div> </motion.div>
<div className="grid lg:grid-cols-3 gap-12"> <div className="grid lg:grid-cols-3 gap-12">
@@ -73,7 +99,7 @@ export default function GuideSection() {
key={i} key={i}
initial={{ opacity: 0, y: 12 }} initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: false }}
transition={{ delay: i * 0.07, duration: 0.4 }} transition={{ delay: i * 0.07, duration: 0.4 }}
> >
<FaqItem faq={faq} index={i} /> <FaqItem faq={faq} index={i} />
@@ -86,43 +112,55 @@ export default function GuideSection() {
<div className="sticky top-24 space-y-4"> <div className="sticky top-24 space-y-4">
{/* Guides card */} {/* Guides card */}
<div className="bg-slate-50 rounded-2xl border border-slate-100 p-6"> <motion.div
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}
transition={{ duration: 0.45, delay: 0.1 }}
className="bg-[#19392e]/80 backdrop-blur-sm rounded-2xl border border-white/10 p-6"
>
<div className="flex items-center gap-2 mb-5"> <div className="flex items-center gap-2 mb-5">
<div className="w-8 h-8 bg-[#9CCC8A]/20 rounded-xl flex items-center justify-center"> <div className="w-8 h-8 bg-[#9CCC8A]/20 rounded-xl flex items-center justify-center">
<FiBookOpen className="text-[#9CCC8A] text-sm" /> <FiBookOpen className="text-[#9CCC8A] text-sm" />
</div> </div>
<h3 className="font-bold text-slate-900">Tous nos guides</h3> <h3 className="font-bold text-[#fffef6]">Tous nos guides</h3>
</div> </div>
<ul className="space-y-1"> <ul className="space-y-1">
{guides.map((guide, i) => ( {guides.map((guide, i) => (
<li key={i}> <li key={i}>
<a <a
href="#" href="#"
className="flex items-start gap-2 text-sm text-slate-600 hover:text-[#9CCC8A] transition-colors py-2 group" className="flex items-start gap-2 text-sm text-[#fffef6]/70 hover:text-[#9CCC8A] transition-colors py-2 group"
> >
<FiChevronRight className="flex-shrink-0 mt-0.5 text-slate-300 group-hover:text-[#9CCC8A] transition-colors" /> <FiChevronRight className="flex-shrink-0 mt-0.5 text-white/20 group-hover:text-[#9CCC8A] transition-colors" />
{guide} {guide}
</a> </a>
</li> </li>
))} ))}
</ul> </ul>
</div> </motion.div>
{/* CTA card */} {/* CTA card */}
<div className="bg-gradient-to-br from-[#9CCC8A] to-[#74AB61] rounded-2xl p-6 text-white"> <motion.div
<div className="font-extrabold text-lg mb-2"> initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}
transition={{ duration: 0.45, delay: 0.2 }}
className="bg-[#19392e]/80 backdrop-blur-sm rounded-2xl border border-white/10 p-6"
>
<div className="font-extrabold text-lg mb-2 text-[#fffef6]">
Besoin d'un conseil personnalisé ? Besoin d'un conseil personnalisé ?
</div> </div>
<p className="text-white/70 text-sm mb-5 leading-relaxed"> <p className="text-[#fffef6]/60 text-sm mb-5 leading-relaxed">
Nos experts répondent à toutes vos questions gratuitement, sans engagement. Nos experts répondent à toutes vos questions gratuitement, sans engagement.
</p> </p>
<a <a
href="tel:+33180891030" href="tel:+33180891030"
className="flex items-center justify-center gap-2 bg-white text-[#74AB61] font-bold text-sm py-3 rounded-xl hover:bg-[#9CCC8A]/10 transition-colors" className="flex items-center justify-center gap-2 bg-[#9CCC8A] hover:bg-[#87BE73] text-black font-bold text-sm py-3 rounded-xl transition-colors"
> >
📞 01 80 89 10 30 📞 01 80 89 10 30
</a> </a>
</div> </motion.div>
</div> </div>
</div> </div>
+85 -69
View File
@@ -30,7 +30,7 @@ function VideoPlayer({ src }) {
} }
return ( return (
<div className="relative rounded-2xl overflow-hidden shadow-2xl shadow-[#9CCC8A]/30 bg-slate-900 aspect-video w-full"> <div className="relative rounded-2xl overflow-hidden shadow-2xl shadow-black/30 bg-slate-900 w-full h-full min-h-[320px]">
{src ? ( {src ? (
<video <video
ref={videoRef} ref={videoRef}
@@ -44,13 +44,11 @@ function VideoPlayer({ src }) {
className="w-full h-full object-cover cursor-pointer" className="w-full h-full object-cover cursor-pointer"
/> />
) : ( ) : (
/* Placeholder pendant le chargement de config.json */
<div className="w-full h-full flex items-center justify-center bg-slate-800 animate-pulse"> <div className="w-full h-full flex items-center justify-center bg-slate-800 animate-pulse">
<FiPlay className="text-slate-600 text-5xl" /> <FiPlay className="text-slate-600 text-5xl" />
</div> </div>
)} )}
{/* Icône play/pause au centre au clic */}
{src && !playing && ( {src && !playing && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center"> <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="w-16 h-16 rounded-full bg-black/50 flex items-center justify-center backdrop-blur-sm"> <div className="w-16 h-16 rounded-full bg-black/50 flex items-center justify-center backdrop-blur-sm">
@@ -59,7 +57,6 @@ function VideoPlayer({ src }) {
</div> </div>
)} )}
{/* Contrôles : pause + mute */}
{src && ( {src && (
<div className="absolute bottom-3 right-3 flex gap-2"> <div className="absolute bottom-3 right-3 flex gap-2">
<button <button
@@ -82,27 +79,23 @@ function VideoPlayer({ src }) {
) )
} }
export default function Hero() { export default function Hero({ bgSrc }) {
const [videoSrc, setVideoSrc] = useState(null) const [videoSrc, setVideoSrc] = useState(null)
const [bgSrc, setBgSrc] = useState(null)
useEffect(() => { useEffect(() => {
fetch('/config.json') fetch('/config.json')
.then(r => r.json()) .then(r => r.json())
.then(data => { .then(data => setVideoSrc(data.heroVideo ?? null))
setVideoSrc(data.heroVideo)
setBgSrc(data.heroBg ?? null)
})
.catch(() => {}) .catch(() => {})
}, []) }, [])
return ( return (
<section <section
className="relative overflow-hidden bg-cover bg-center pt-24 pb-20" className="relative bg-cover bg-center bg-fixed pt-28 pb-48"
style={bgSrc ? { backgroundImage: `url(${bgSrc})` } : {}} style={bgSrc ? { backgroundImage: `url(${bgSrc})` } : {}}
> >
{/* Gradient overlay semi-transparent (couvre l'image de fond) */} {/* Overlay */}
<div className="pointer-events-none absolute inset-0 bg-gradient-to-br from-white/88 via-[#9CCC8A]/18 to-white/75" /> <div className="pointer-events-none absolute inset-0 bg-[#19392e]/70 backdrop-blur-[2px]" />
{/* Blobs décoratifs */} {/* Blobs décoratifs */}
<div className="pointer-events-none absolute inset-0 overflow-hidden"> <div className="pointer-events-none absolute inset-0 overflow-hidden">
@@ -111,84 +104,107 @@ export default function Hero() {
</div> </div>
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex flex-col lg:flex-row lg:items-center gap-10 lg:gap-16">
{/* ── Colonne gauche : texte ── */} {/* ── Titre + tagline centrés ── */}
<motion.div <motion.div
initial={{ opacity: 0, y: 24 }} initial={{ opacity: 0, y: 28 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.55 }} transition={{ duration: 0.55 }}
className="flex-1 flex flex-col" className="text-center mb-12"
> >
<h1 className="text-5xl xl:text-6xl font-extrabold text-slate-900 leading-[1.08] tracking-tight mb-5"> <h1 className="text-5xl xl:text-7xl font-extrabold text-[#fffef6] leading-[1.05] tracking-tight mb-5">
Mutuelle santé{' '} Mutuelle santé{' '}
<span className="relative"> <span className="relative inline-block">
<span className="relative z-10 text-transparent bg-clip-text bg-gradient-to-r from-[#9CCC8A] to-[#5BAF7A]"> <span className="relative z-10 text-transparent bg-clip-text bg-gradient-to-r from-[#9CCC8A] to-[#5BAF7A]">
pour tous pour tous
</span>
<span className="absolute bottom-1 left-0 w-full h-3 bg-[#9CCC8A]/30 rounded-sm -z-0" />
</span> </span>
</h1> <span className="absolute bottom-1 left-0 w-full h-3 bg-[#9CCC8A]/30 rounded-sm -z-0" />
</span>
</h1>
<p className="text-lg text-black leading-relaxed mb-8 max-w-lg"> <motion.div
Jeune actif, parent, senior comparez les garanties utiles et trouvez initial={{ opacity: 0, y: 12 }}
la couverture parfaite au meilleur prix, sans engagement. animate={{ opacity: 1, y: 0 }}
</p> transition={{ duration: 0.45, delay: 0.2 }}
className="inline-block bg-[#19392e]/80 backdrop-blur-sm border border-white/20 text-[#fffef6] text-sm font-semibold px-5 py-2.5 rounded-full shadow-lg"
>
Payez moins. Le reste, on s'en bat les clauses.
</motion.div>
</motion.div>
{/* Trust points */} {/* ── Deux colonnes : carte gauche + vidéo droite ── */}
<ul className="flex flex-wrap gap-x-6 gap-y-1.5 mb-8"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-10">
{['Comparaison 100% gratuite', 'Sans engagement', 'Réponse en 2 min'].map(t => (
<li key={t} className="flex items-center gap-1.5 text-sm text-black">
<FiCheckCircle className="text-emerald-500 flex-shrink-0" />
{t}
</li>
))}
</ul>
{/* CTAs */} {/* Carte gauche */}
<div className="flex flex-wrap gap-3 mb-14"> <motion.div
<motion.button initial={{ opacity: 0, x: -28 }}
whileHover={{ scale: 1.025 }} animate={{ opacity: 1, x: 0 }}
whileTap={{ scale: 0.97 }} transition={{ duration: 0.55, delay: 0.3 }}
className="flex items-center gap-2 bg-[#9CCC8A] hover:bg-[#87BE73] text-black font-bold text-base px-7 py-3.5 rounded-xl shadow-lg shadow-[#9CCC8A]/40 transition-all" className="bg-[#19392e]/80 backdrop-blur-sm rounded-3xl border border-white/10 shadow-xl shadow-black/30 p-8 flex flex-col justify-between"
> >
Comparer les mutuelles <div>
<FiArrowRight /> <p className="text-lg text-[#fffef6]/80 leading-relaxed mb-6">
</motion.button> Jeune actif, parent, senior — comparez les garanties utiles et trouvez
<button className="flex items-center gap-2 font-semibold text-slate-700 text-base px-7 py-3.5 rounded-xl border border-slate-200 bg-white hover:border-[#9CCC8A] hover:text-[#9CCC8A] transition-all"> la couverture parfaite au meilleur prix, sans engagement.
Voir le classement </p>
</button>
{/* Trust points */}
<ul className="flex flex-col gap-2 mb-8">
{['Comparaison 100% gratuite', 'Sans engagement', 'Réponse en 2 min'].map(t => (
<li key={t} className="flex items-center gap-2 text-sm text-[#fffef6]/80">
<FiCheckCircle className="text-[#9CCC8A] flex-shrink-0" size={15} />
{t}
</li>
))}
</ul>
{/* CTAs */}
<div className="flex flex-wrap gap-3 mb-8">
<motion.button
whileHover={{ scale: 1.025 }}
whileTap={{ scale: 0.97 }}
className="flex items-center gap-2 bg-[#9CCC8A] hover:bg-[#87BE73] text-black font-bold text-base px-7 py-3.5 rounded-xl shadow-lg shadow-[#9CCC8A]/40 transition-all"
>
Comparer les mutuelles
<FiArrowRight />
</motion.button>
<button className="flex items-center gap-2 font-semibold text-[#fffef6]/80 text-base px-7 py-3.5 rounded-xl border border-white/20 bg-white/5 hover:border-[#9CCC8A] hover:text-[#9CCC8A] transition-all">
Voir le classement
</button>
</div>
</div> </div>
{/* Stats bar */} {/* Stats bar */}
<motion.div <div className="grid grid-cols-3 gap-4 pt-6 border-t border-white/10">
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.55, delay: 0.35 }}
className="grid grid-cols-3 gap-6 max-w-xs"
>
{stats.map(({ value, label, icon: Icon }) => ( {stats.map(({ value, label, icon: Icon }) => (
<div key={label} className="flex flex-col gap-1"> <div key={label} className="flex flex-col gap-1">
<Icon className="text-white text-lg mb-0.5" /> <Icon className="text-[#9CCC8A] text-base mb-0.5" />
<div className="text-2xl font-extrabold text-slate-900">{value}</div> <div className="text-2xl font-extrabold text-[#fffef6]">{value}</div>
<div className="text-xs text-black font-medium">{label}</div> <div className="text-xs text-[#fffef6]/50 font-medium">{label}</div>
</div> </div>
))} ))}
</motion.div> </div>
</motion.div> </motion.div>
{/* ── Colonne droite : vidéo ── */} {/* Vidéo droite */}
<motion.div <motion.div
initial={{ opacity: 0, x: 32 }} initial={{ opacity: 0, x: 28 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.55, delay: 0.2 }} transition={{ duration: 0.55, delay: 0.35 }}
className="w-full lg:w-[520px] xl:w-[580px] flex-shrink-0" className="min-h-0"
> >
<VideoPlayer src={videoSrc} /> <VideoPlayer src={videoSrc} />
</motion.div> </motion.div>
</div> </div>
</div> </div>
{/* Wave → ComparisonSection */}
<div className="absolute bottom-0 left-0 w-full overflow-hidden leading-[0] pointer-events-none">
<svg className="block w-full h-20 fill-white" viewBox="0 0 1200 120" preserveAspectRatio="none" style={{ transform: 'scaleY(-1)' }}>
<path d="M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43,512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34C989.49,25,1113-14.29,1200,52.47V0Z" />
</svg>
</div>
</section> </section>
) )
} }
+7 -8
View File
@@ -1,6 +1,7 @@
import { useState } from 'react' import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { FiPhone, FiMenu, FiX } from 'react-icons/fi' import { FiPhone, FiMenu, FiX } from 'react-icons/fi'
import AnimatedBrandName from './AnimatedBrandName'
const navLinks = [ const navLinks = [
{ label: 'Mutuelle santé', href: '#' }, { label: 'Mutuelle santé', href: '#' },
@@ -15,7 +16,7 @@ export default function Navbar() {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
return ( return (
<header className="sticky top-0 z-50 bg-white/95 backdrop-blur border-b border-slate-100 shadow-sm"> <header className="sticky top-0 z-50 bg-[#19392e] border-b border-white/10 shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16"> <div className="flex items-center justify-between h-16">
{/* Logo */} {/* Logo */}
@@ -25,9 +26,7 @@ export default function Navbar() {
alt="AssurezMoi" alt="AssurezMoi"
className="w-9 h-9 rounded-xl object-contain" className="w-9 h-9 rounded-xl object-contain"
/> />
<span className="font-extrabold text-xl tracking-tight text-slate-900"> <AnimatedBrandName lightText />
Assurez<span className="text-[#9CCC8A]">Moi</span>
</span>
</a> </a>
{/* Desktop nav */} {/* Desktop nav */}
@@ -36,7 +35,7 @@ export default function Navbar() {
<a <a
key={link.label} key={link.label}
href={link.href} href={link.href}
className="px-3 py-2 text-sm text-slate-600 hover:text-[#9CCC8A] hover:bg-[#9CCC8A]/10 rounded-lg transition-all font-medium" className="px-3 py-2 text-sm text-[#fffef6]/80 hover:text-[#9CCC8A] hover:bg-[#9CCC8A]/10 rounded-lg transition-all font-medium"
> >
{link.label} {link.label}
</a> </a>
@@ -56,7 +55,7 @@ export default function Navbar() {
{/* Mobile toggle */} {/* Mobile toggle */}
<button <button
className="lg:hidden p-2 rounded-lg text-slate-600 hover:bg-slate-100 transition-colors" className="lg:hidden p-2 rounded-lg text-[#fffef6] hover:bg-white/10 transition-colors"
onClick={() => setOpen((v) => !v)} onClick={() => setOpen((v) => !v)}
aria-label="Menu" aria-label="Menu"
> >
@@ -73,13 +72,13 @@ export default function Navbar() {
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }} exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.18 }} transition={{ duration: 0.18 }}
className="lg:hidden border-t border-slate-100 bg-white px-4 pt-3 pb-5 space-y-1" className="lg:hidden border-t border-white/10 bg-[#19392e] px-4 pt-3 pb-5 space-y-1"
> >
{navLinks.map((link) => ( {navLinks.map((link) => (
<a <a
key={link.label} key={link.label}
href={link.href} href={link.href}
className="block px-3 py-2.5 text-sm text-slate-600 hover:text-[#9CCC8A] hover:bg-[#9CCC8A]/10 rounded-lg font-medium transition-colors" className="block px-3 py-2.5 text-sm text-[#fffef6]/80 hover:text-[#9CCC8A] hover:bg-[#9CCC8A]/10 rounded-lg font-medium transition-colors"
> >
{link.label} {link.label}
</a> </a>
+10 -4
View File
@@ -22,7 +22,13 @@ export default function Testimonials() {
<section className="py-20 bg-slate-50"> <section className="py-20 bg-slate-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-14"> <motion.div
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: false }}
transition={{ duration: 0.5 }}
className="text-center mb-14"
>
<h2 className="text-4xl font-extrabold text-slate-900 tracking-tight mb-4"> <h2 className="text-4xl font-extrabold text-slate-900 tracking-tight mb-4">
Ce que nos clients disent de nous Ce que nos clients disent de nous
</h2> </h2>
@@ -30,7 +36,7 @@ export default function Testimonials() {
Découvrez les témoignages de clients satisfaits qui nous font confiance Découvrez les témoignages de clients satisfaits qui nous font confiance
pour leurs assurances santé. pour leurs assurances santé.
</p> </p>
</div> </motion.div>
<div className="grid md:grid-cols-3 gap-6"> <div className="grid md:grid-cols-3 gap-6">
{testimonials.map((t, i) => ( {testimonials.map((t, i) => (
@@ -38,7 +44,7 @@ export default function Testimonials() {
key={t.id} key={t.id}
initial={{ opacity: 0, y: 24 }} initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: false }}
transition={{ delay: i * 0.12, duration: 0.45 }} transition={{ delay: i * 0.12, duration: 0.45 }}
className="bg-white rounded-2xl border border-slate-100 p-6 shadow-sm hover:shadow-md hover:-translate-y-0.5 transition-all" className="bg-white rounded-2xl border border-slate-100 p-6 shadow-sm hover:shadow-md hover:-translate-y-0.5 transition-all"
> >
@@ -75,7 +81,7 @@ export default function Testimonials() {
<motion.div <motion.div
initial={{ opacity: 0, y: 16 }} initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: false }}
transition={{ duration: 0.45, delay: 0.3 }} transition={{ duration: 0.45, delay: 0.3 }}
className="mt-12 flex flex-col sm:flex-row items-center justify-center gap-4 bg-white rounded-2xl border border-slate-100 shadow-sm px-8 py-5" className="mt-12 flex flex-col sm:flex-row items-center justify-center gap-4 bg-white rounded-2xl border border-slate-100 shadow-sm px-8 py-5"
> >
+2
View File
@@ -8,6 +8,8 @@
font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
background-color: #19392e;
color: #fffef6;
} }
* { * {
box-sizing: border-box; box-sizing: border-box;