This commit is contained in:
lfirmin
2026-02-25 19:32:34 +01:00
parent 344ad83164
commit 338c249e49
14 changed files with 1789 additions and 96 deletions
+17 -31
View File
@@ -1,35 +1,21 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0)
import Navbar from './components/Navbar'
import Hero from './components/Hero'
import ComparisonSection from './components/ComparisonSection'
import Testimonials from './components/Testimonials'
import GuideSection from './components/GuideSection'
import Footer from './components/Footer'
export default function App() {
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
<div className="min-h-screen bg-white">
<Navbar />
<main>
<Hero />
<ComparisonSection />
<Testimonials />
<GuideSection />
</main>
<Footer />
</div>
)
}
export default App
+143
View File
@@ -0,0 +1,143 @@
import { motion } from 'framer-motion'
import { FiAward, FiArrowRight } from 'react-icons/fi'
import { mutuelles } from '../data/mutuelles'
import MutuelleCard from './MutuelleCard'
const medals = ['🏆', '🥈', '🥉']
function TopCard({ mutuelle, index }) {
const isFirst = index === 0
return (
<div
className={`relative flex flex-col rounded-3xl border-2 p-6 transition-all hover:shadow-xl hover:-translate-y-1 cursor-pointer ${
isFirst
? 'border-indigo-300 bg-gradient-to-b from-indigo-50 to-white shadow-lg shadow-indigo-100'
: 'border-slate-100 bg-white hover:border-slate-200 shadow-sm'
}`}
>
{/* Badge */}
{isFirst && (
<div className="absolute -top-3.5 left-1/2 -translate-x-1/2 bg-gradient-to-r from-indigo-600 to-indigo-700 text-white text-[11px] font-extrabold px-4 py-1.5 rounded-full shadow-md whitespace-nowrap">
Meilleur Choix 2026
</div>
)}
<div className="flex items-start justify-between mb-5 mt-1">
{/* Logo */}
<div
className="w-14 h-14 rounded-2xl flex items-center justify-center font-extrabold text-white text-lg shadow-md"
style={{ backgroundColor: mutuelle.primaryColor }}
>
{mutuelle.initials}
</div>
{/* Medal + rank */}
<span className="text-2xl">{medals[index]}</span>
</div>
<div className="font-extrabold text-slate-900 text-base mb-0.5">{mutuelle.name}</div>
<div className="text-xs text-slate-500 mb-4">{mutuelle.tagline}</div>
{/* Tags */}
<div className="flex gap-1.5 flex-wrap mb-5">
{mutuelle.idealFor.map((tag) => (
<span
key={tag}
className="text-[11px] font-medium px-2 py-0.5 rounded-full"
style={{ backgroundColor: mutuelle.lightBg, color: mutuelle.primaryColor }}
>
{tag}
</span>
))}
</div>
{/* Promo */}
{mutuelle.offer && (
<div className="mb-4 bg-emerald-50 border border-emerald-200 text-emerald-700 text-xs font-bold text-center py-2 rounded-xl">
🎁 {mutuelle.offer}
</div>
)}
{/* Score + CTA */}
<div className="flex items-end justify-between mt-auto pt-4 border-t border-slate-100">
<div>
<div className="text-3xl font-extrabold" style={{ color: mutuelle.primaryColor }}>
{mutuelle.score}
<span className="text-sm font-medium text-slate-400"> /10</span>
</div>
<div className="text-[10px] text-slate-400 font-medium mt-0.5">Score Mon Gaston</div>
</div>
<button
className="flex items-center gap-1.5 text-sm font-bold text-white px-4 py-2 rounded-xl hover:opacity-90 transition-opacity shadow-md"
style={{ backgroundColor: mutuelle.primaryColor }}
>
Voir l'offre <FiArrowRight size={13} />
</button>
</div>
</div>
)
}
export default function ComparisonSection() {
return (
<section id="classement" className="pt-10 pb-20 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Header */}
<div 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">
<FiAward size={13} />
Classement officiel 2026
</div>
<h2 className="text-4xl font-extrabold text-slate-900 tracking-tight mb-4">
Les meilleures mutuelles santé
</h2>
<p className="text-slate-500 max-w-xl mx-auto leading-relaxed">
Classement mis à jour chaque mois basé sur les garanties, les tarifs
et les avis clients de plus de 186 mutuelles analysées.
</p>
</div>
{/* Top 3 grid */}
<div className="grid md:grid-cols-3 gap-6 mb-14">
{mutuelles.slice(0, 3).map((m, i) => (
<motion.div
key={m.id}
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.45 }}
>
<TopCard mutuelle={m} index={i} />
</motion.div>
))}
</div>
{/* Divider */}
<div className="flex items-center gap-4 mb-8">
<div className="flex-1 h-px bg-slate-100" />
<span className="text-xs font-bold text-slate-400 uppercase tracking-widest">
Classement complet
</span>
<div className="flex-1 h-px bg-slate-100" />
</div>
{/* Full list */}
<div className="space-y-3">
{mutuelles.map((m, i) => (
<motion.div
key={m.id}
initial={{ opacity: 0, x: -16 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.06, duration: 0.4 }}
>
<MutuelleCard mutuelle={m} />
</motion.div>
))}
</div>
</div>
</section>
)
}
+157
View File
@@ -0,0 +1,157 @@
import { MdPets } from 'react-icons/md'
import {
FiFacebook,
FiTwitter,
FiInstagram,
FiLinkedin,
FiYoutube,
FiArrowUpRight,
} from 'react-icons/fi'
const colResources = ['FAQ', 'Guides', 'Lexique', 'Documents']
const colDiscover = [
'À propos',
'Fonctionnement',
'Revue de presse',
'Nous rejoindre',
'Nos partenaires',
'Espace presse',
]
const colLegal = [
'Mentions légales',
'Politique de confidentialité',
'Gestion des cookies',
"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() {
return (
<footer className="bg-slate-900 text-slate-400">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Main grid */}
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-10 py-16">
{/* Brand */}
<div className="sm:col-span-2 lg:col-span-1">
<div className="flex items-center gap-2.5 mb-5">
<div className="w-9 h-9 bg-indigo-500 rounded-xl flex items-center justify-center shadow-md">
<MdPets className="text-white text-lg" />
</div>
<span className="font-extrabold text-xl text-white tracking-tight">
Mon <span className="text-indigo-400">Gaston</span>
</span>
</div>
<address className="not-italic text-sm text-slate-500 leading-relaxed mb-4">
Votre comparateur d'assurances<br />
33 avenue Roger Salengro<br />
59100 Roubaix, France
</address>
<a
href="mailto:[email protected]"
className="text-sm text-slate-500 hover:text-indigo-400 transition-colors"
>
contact@mongaston.com
</a>
<div className="mt-2">
<a
href="tel:+33180891030"
className="text-sm text-slate-500 hover:text-indigo-400 transition-colors"
>
01 80 89 10 30
</a>
</div>
</div>
{/* Resources */}
<div>
<h4 className="text-white font-bold text-sm uppercase tracking-widest mb-5">
Ressources
</h4>
<ul className="space-y-3">
{colResources.map((r) => (
<li key={r}>
<a
href="#"
className="text-sm text-slate-500 hover:text-white transition-colors flex items-center gap-1 group"
>
{r}
<FiArrowUpRight size={11} className="opacity-0 group-hover:opacity-100 transition-opacity" />
</a>
</li>
))}
</ul>
</div>
{/* Discover */}
<div>
<h4 className="text-white font-bold text-sm uppercase tracking-widest mb-5">
Nous découvrir
</h4>
<ul className="space-y-3">
{colDiscover.map((d) => (
<li key={d}>
<a
href="#"
className="text-sm text-slate-500 hover:text-white transition-colors flex items-center gap-1 group"
>
{d}
<FiArrowUpRight size={11} className="opacity-0 group-hover:opacity-100 transition-opacity" />
</a>
</li>
))}
</ul>
</div>
{/* Legal */}
<div>
<h4 className="text-white font-bold text-sm uppercase tracking-widest mb-5">
Informations légales
</h4>
<ul className="space-y-3">
{colLegal.map((l) => (
<li key={l}>
<a href="#" className="text-sm text-slate-500 hover:text-white transition-colors">
{l}
</a>
</li>
))}
</ul>
</div>
</div>
{/* Bottom bar */}
<div className="border-t border-slate-800 py-6 flex flex-col sm:flex-row items-center justify-between gap-4">
<p className="text-xs text-slate-600 text-center sm:text-left">
© 2026 Mon Gaston Tous droits réservés.
<span className="mx-2 text-slate-700">·</span>
Comparateur de mutuelles santé & assurances
</p>
<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-slate-800 hover:bg-indigo-600 flex items-center justify-center transition-all hover:-translate-y-0.5"
>
<Icon size={14} className="text-slate-400 hover:text-white" />
</a>
))}
</div>
</div>
</div>
</footer>
)
}
+134
View File
@@ -0,0 +1,134 @@
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { FiChevronDown, FiBookOpen, FiChevronRight } from 'react-icons/fi'
import { faqs, guides } from '../data/mutuelles'
function FaqItem({ faq, index }) {
const [open, setOpen] = useState(index === 0)
return (
<div className="border border-slate-100 rounded-2xl overflow-hidden">
<button
onClick={() => setOpen((v) => !v)}
className="w-full flex items-start gap-4 px-6 py-5 text-left hover:bg-slate-50 transition-colors"
>
<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"
style={{ backgroundColor: open ? '#4F46E5' : '#CBD5E1' }}
>
{index + 1}
</span>
<span className="flex-1 font-bold text-slate-900 leading-snug">{faq.question}</span>
<motion.div
animate={{ rotate: open ? 180 : 0 }}
transition={{ duration: 0.2 }}
className="flex-shrink-0 mt-0.5"
>
<FiChevronDown className={`text-lg transition-colors ${open ? 'text-indigo-500' : 'text-slate-400'}`} />
</motion.div>
</button>
<AnimatePresence initial={false}>
{open && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25 }}
>
<div className="px-6 pb-6 space-y-3 border-t border-slate-100 pt-4">
{faq.content.map((para, i) => (
<p key={i} className="text-sm text-slate-500 leading-relaxed">
{para}
</p>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
)
}
export default function GuideSection() {
return (
<section className="py-20 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-14">
<h2 className="text-4xl font-extrabold text-slate-900 tracking-tight mb-4">
Tout savoir sur la mutuelle santé
</h2>
<p className="text-slate-500 max-w-xl mx-auto">
Nos guides répondent à toutes vos questions pour faire le bon choix.
</p>
</div>
<div className="grid lg:grid-cols-3 gap-12">
{/* FAQ accordéon */}
<div className="lg:col-span-2 space-y-3">
{faqs.map((faq, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.07, duration: 0.4 }}
>
<FaqItem faq={faq} index={i} />
</motion.div>
))}
</div>
{/* Sidebar */}
<div className="lg:col-span-1">
<div className="sticky top-24 space-y-4">
{/* Guides card */}
<div className="bg-slate-50 rounded-2xl border border-slate-100 p-6">
<div className="flex items-center gap-2 mb-5">
<div className="w-8 h-8 bg-indigo-100 rounded-xl flex items-center justify-center">
<FiBookOpen className="text-indigo-600 text-sm" />
</div>
<h3 className="font-bold text-slate-900">Tous nos guides</h3>
</div>
<ul className="space-y-1">
{guides.map((guide, i) => (
<li key={i}>
<a
href="#"
className="flex items-start gap-2 text-sm text-slate-600 hover:text-indigo-600 transition-colors py-2 group"
>
<FiChevronRight className="flex-shrink-0 mt-0.5 text-slate-300 group-hover:text-indigo-400 transition-colors" />
{guide}
</a>
</li>
))}
</ul>
</div>
{/* CTA card */}
<div className="bg-gradient-to-br from-indigo-600 to-indigo-700 rounded-2xl p-6 text-white">
<div className="font-extrabold text-lg mb-2">
Besoin d'un conseil personnalisé ?
</div>
<p className="text-indigo-200 text-sm mb-5 leading-relaxed">
Nos experts répondent à toutes vos questions gratuitement, sans engagement.
</p>
<a
href="tel:+33180891030"
className="flex items-center justify-center gap-2 bg-white text-indigo-700 font-bold text-sm py-3 rounded-xl hover:bg-indigo-50 transition-colors"
>
📞 01 80 89 10 30
</a>
</div>
</div>
</div>
</div>
</div>
</section>
)
}
+85
View File
@@ -0,0 +1,85 @@
import { motion } from 'framer-motion'
import { FiArrowRight, FiShield, FiStar, FiUsers, FiCheckCircle } from 'react-icons/fi'
const stats = [
{ value: '186', label: 'mutuelles analysées', icon: FiShield },
{ value: '4.9 / 5', label: 'note clients', icon: FiStar },
{ value: '250 000+', label: 'utilisateurs', icon: FiUsers },
]
export default function Hero() {
return (
<section className="relative overflow-hidden bg-gradient-to-br from-slate-50 via-indigo-50/40 to-white pt-14 pb-10">
{/* Decorative blobs */}
<div className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="absolute -top-48 -right-48 w-[600px] h-[600px] rounded-full bg-indigo-100/50 blur-3xl" />
<div className="absolute bottom-0 -left-32 w-[400px] h-[400px] rounded-full bg-sky-100/40 blur-3xl" />
</div>
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-col items-center text-center">
<motion.div
initial={{ opacity: 0, y: 24 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.55 }}
className="max-w-3xl"
>
<h1 className="text-5xl xl:text-6xl font-extrabold text-slate-900 leading-[1.08] tracking-tight mb-5">
Mutuelle santé{' '}
<span className="relative">
<span className="relative z-10 text-transparent bg-clip-text bg-gradient-to-r from-indigo-600 to-sky-500">
pour tous
</span>
<span className="absolute bottom-1 left-0 w-full h-3 bg-indigo-100/70 rounded-sm -z-0" />
</span>
</h1>
<p className="text-lg text-slate-500 leading-relaxed mb-8 max-w-lg mx-auto">
Jeune actif, parent, senior comparez les garanties utiles et trouvez
la couverture parfaite au meilleur prix, sans engagement.
</p>
{/* Trust points */}
<ul className="flex flex-wrap justify-center gap-x-6 gap-y-1.5 mb-8">
{['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-slate-500">
<FiCheckCircle className="text-emerald-500 flex-shrink-0" />
{t}
</li>
))}
</ul>
{/* CTA */}
<div className="flex flex-wrap justify-center gap-3">
<motion.button
whileHover={{ scale: 1.025 }}
whileTap={{ scale: 0.97 }}
className="flex items-center gap-2 bg-gradient-to-r from-indigo-600 to-indigo-700 hover:from-indigo-700 hover:to-indigo-800 text-white font-bold text-base px-7 py-3.5 rounded-xl shadow-lg shadow-indigo-300/50 transition-all"
>
Comparer les mutuelles
<FiArrowRight />
</motion.button>
<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-indigo-300 hover:text-indigo-600 transition-all">
Voir le classement
</button>
</div>
</motion.div>
{/* Stats bar */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.55, delay: 0.35 }}
className="mt-16 grid grid-cols-3 gap-6 max-w-2xl"
>
{stats.map(({ value, label, icon: Icon }) => (
<div key={label} className="flex flex-col items-center gap-1">
<Icon className="text-indigo-400 text-lg mb-0.5" />
<div className="text-2xl font-extrabold text-slate-900">{value}</div>
<div className="text-xs text-slate-400 font-medium">{label}</div>
</div>
))}
</motion.div>
</div>
</section>
)
}
+142
View File
@@ -0,0 +1,142 @@
import { FiArrowRight, FiActivity, FiEye, FiSmile } from 'react-icons/fi'
import { MdLocalHospital } from 'react-icons/md'
const hospConfig = {
orange: {
bg: 'bg-orange-50',
text: 'text-orange-700',
border: 'border-orange-200',
},
green: {
bg: 'bg-emerald-50',
text: 'text-emerald-700',
border: 'border-emerald-200',
},
teal: {
bg: 'bg-teal-50',
text: 'text-teal-700',
border: 'border-teal-200',
},
}
function FeatureDots({ count }) {
return (
<div className="flex gap-1">
{[1, 2, 3].map((i) => (
<div
key={i}
className={`w-2.5 h-2.5 rounded-full transition-colors ${
i <= count ? 'bg-indigo-500' : 'bg-slate-200'
}`}
/>
))}
</div>
)
}
function ScoreBar({ score, color }) {
return (
<div className="flex flex-col items-center gap-1 min-w-[64px]">
<div className="text-xl font-extrabold" style={{ color }}>
{score}
</div>
<div className="w-14 h-1.5 bg-slate-100 rounded-full overflow-hidden">
<div
className="h-full rounded-full"
style={{ width: `${(score / 10) * 100}%`, backgroundColor: color }}
/>
</div>
<div className="text-[10px] text-slate-400 font-medium whitespace-nowrap">
Score Mon Gaston
</div>
</div>
)
}
export default function MutuelleCard({ mutuelle }) {
const hosp = hospConfig[mutuelle.hospColor] || hospConfig.teal
const isFirst = mutuelle.rank === 1
return (
<div
className={`bg-white rounded-2xl border transition-all hover:shadow-lg hover:-translate-y-0.5 p-5 flex items-center gap-4 ${
isFirst
? 'border-indigo-200 shadow-md ring-1 ring-indigo-100'
: 'border-slate-100 hover:border-slate-200'
}`}
>
{/* Rank badge */}
<div
className={`w-8 h-8 rounded-xl flex items-center justify-center text-sm font-bold flex-shrink-0 ${
isFirst ? 'bg-indigo-600 text-white shadow-md shadow-indigo-300' : 'bg-slate-100 text-slate-500'
}`}
>
{mutuelle.rank}
</div>
{/* Logo */}
<div
className="w-14 h-14 rounded-2xl flex items-center justify-center font-extrabold text-white text-sm flex-shrink-0 shadow-md"
style={{ backgroundColor: mutuelle.primaryColor }}
>
{mutuelle.initials}
</div>
{/* Name + tags */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap mb-1">
<span className="font-bold text-slate-900 truncate">{mutuelle.name}</span>
{isFirst && (
<span className="bg-amber-50 text-amber-600 border border-amber-200 text-[10px] font-extrabold px-2 py-0.5 rounded-full whitespace-nowrap">
Meilleur Choix
</span>
)}
</div>
<div className="flex items-center gap-1.5 flex-wrap">
{mutuelle.idealFor.map((tag) => (
<span
key={tag}
className="text-[11px] font-medium bg-slate-100 text-slate-500 px-2 py-0.5 rounded-full"
>
{tag}
</span>
))}
</div>
</div>
{/* Features */}
<div className="hidden md:flex flex-col gap-2 flex-shrink-0">
<div className="flex items-center gap-2">
<FiActivity className="text-slate-300 text-xs flex-shrink-0" />
<span className="text-[11px] text-slate-500 w-12">Soins</span>
<FeatureDots count={mutuelle.soins} />
</div>
<div className="flex items-center gap-2">
<FiSmile className="text-slate-300 text-xs flex-shrink-0" />
<span className="text-[11px] text-slate-500 w-12">Dentaire</span>
<FeatureDots count={mutuelle.dentaire} />
</div>
<div className="flex items-center gap-2">
<FiEye className="text-slate-300 text-xs flex-shrink-0" />
<span className="text-[11px] text-slate-500 w-12">Optique</span>
<FeatureDots count={mutuelle.optique} />
</div>
</div>
{/* Score */}
<div className="hidden lg:block flex-shrink-0">
<ScoreBar score={mutuelle.score} color={mutuelle.primaryColor} />
</div>
{/* CTA */}
<button
className="flex items-center gap-2 text-white font-bold text-sm px-5 py-2.5 rounded-xl flex-shrink-0 hover:opacity-90 active:scale-95 transition-all shadow-md"
style={{ backgroundColor: mutuelle.primaryColor }}
>
Voir l'offre
<FiArrowRight />
</button>
</div>
)
}
+98
View File
@@ -0,0 +1,98 @@
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { FiPhone, FiMenu, FiX } from 'react-icons/fi'
import { MdPets } from 'react-icons/md'
const navLinks = [
{ label: 'Mutuelle santé', href: '#' },
{ label: 'Assurance animaux', href: '#' },
{ label: 'Assurance auto', href: '#' },
{ label: 'Assurance habitation', href: '#' },
{ label: 'Assurance Emprunteur', href: '#' },
{ label: 'Contact', href: '#' },
]
export default function Navbar() {
const [open, setOpen] = useState(false)
return (
<header className="sticky top-0 z-50 bg-white/95 backdrop-blur border-b border-slate-100 shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
{/* Logo */}
<a href="#" className="flex items-center gap-2.5">
<div className="w-9 h-9 bg-gradient-to-br from-indigo-500 to-indigo-700 rounded-xl flex items-center justify-center shadow-md shadow-indigo-200">
<MdPets className="text-white text-lg" />
</div>
<span className="font-extrabold text-xl tracking-tight text-slate-900">
Mon <span className="text-indigo-600">Gaston</span>
</span>
</a>
{/* Desktop nav */}
<nav className="hidden lg:flex items-center gap-1">
{navLinks.map((link) => (
<a
key={link.label}
href={link.href}
className="px-3 py-2 text-sm text-slate-600 hover:text-indigo-600 hover:bg-indigo-50 rounded-lg transition-all font-medium"
>
{link.label}
</a>
))}
</nav>
{/* Phone CTA */}
<div className="hidden lg:flex items-center gap-3">
<a
href="tel:+33180891030"
className="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800 text-white text-sm font-bold px-4 py-2.5 rounded-xl transition-all shadow-md shadow-indigo-200"
>
<FiPhone className="text-base" />
01 80 89 10 30
</a>
</div>
{/* Mobile toggle */}
<button
className="lg:hidden p-2 rounded-lg text-slate-600 hover:bg-slate-100 transition-colors"
onClick={() => setOpen((v) => !v)}
aria-label="Menu"
>
{open ? <FiX size={22} /> : <FiMenu size={22} />}
</button>
</div>
</div>
{/* Mobile menu */}
<AnimatePresence>
{open && (
<motion.div
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.18 }}
className="lg:hidden border-t border-slate-100 bg-white px-4 pt-3 pb-5 space-y-1"
>
{navLinks.map((link) => (
<a
key={link.label}
href={link.href}
className="block px-3 py-2.5 text-sm text-slate-600 hover:text-indigo-600 hover:bg-indigo-50 rounded-lg font-medium transition-colors"
>
{link.label}
</a>
))}
<a
href="tel:+33180891030"
className="mt-3 flex items-center justify-center gap-2 bg-indigo-600 text-white text-sm font-bold px-4 py-3 rounded-xl w-full"
>
<FiPhone />
01 80 89 10 30
</a>
</motion.div>
)}
</AnimatePresence>
</header>
)
}
+116
View File
@@ -0,0 +1,116 @@
import { motion } from 'framer-motion'
import { FiStar } from 'react-icons/fi'
import { testimonials } from '../data/mutuelles'
function Stars({ rating }) {
return (
<div className="flex gap-0.5">
{[1, 2, 3, 4, 5].map((i) => (
<FiStar
key={i}
size={13}
className={i <= rating ? 'text-amber-400' : 'text-slate-200'}
style={i <= rating ? { fill: 'currentColor' } : {}}
/>
))}
</div>
)
}
export default function Testimonials() {
return (
<section className="py-20 bg-slate-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-14">
<h2 className="text-4xl font-extrabold text-slate-900 tracking-tight mb-4">
Ce que nos clients disent de nous
</h2>
<p className="text-slate-500 max-w-xl mx-auto">
Découvrez les témoignages de clients satisfaits qui nous font confiance
pour leurs assurances santé.
</p>
</div>
<div className="grid md:grid-cols-3 gap-6">
{testimonials.map((t, i) => (
<motion.div
key={t.id}
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
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"
>
{/* Quote mark */}
<div className="text-4xl font-serif text-indigo-100 leading-none mb-2 select-none">
"
</div>
<p className="text-sm text-slate-600 leading-relaxed mb-5">
{t.text}
</p>
{/* Footer */}
<div className="flex items-center justify-between pt-4 border-t border-slate-100">
<div className="flex items-center gap-3">
<div
className="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold text-sm shadow-sm flex-shrink-0"
style={{ backgroundColor: t.avatarColor }}
>
{t.avatar}
</div>
<div>
<div className="font-bold text-sm text-slate-900">{t.name}</div>
<div className="text-xs text-slate-400">{t.role}</div>
</div>
</div>
<Stars rating={t.rating} />
</div>
</motion.div>
))}
</div>
{/* Trust bar */}
<motion.div
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
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"
>
<div className="flex items-center gap-2">
<div className="flex -space-x-2">
{[
{ initials: 'AB', color: '#2563EB' },
{ initials: 'CL', color: '#DC2626' },
{ initials: 'MK', color: '#059669' },
{ initials: 'JD', color: '#7C3AED' },
].map((a) => (
<div
key={a.initials}
className="w-8 h-8 rounded-full border-2 border-white flex items-center justify-center text-white text-[10px] font-bold"
style={{ backgroundColor: a.color }}
>
{a.initials}
</div>
))}
</div>
<span className="text-sm font-bold text-slate-900">250 000+</span>
<span className="text-sm text-slate-500">utilisateurs satisfaits</span>
</div>
<div className="hidden sm:block w-px h-6 bg-slate-200" />
<div className="flex items-center gap-2">
<div className="flex gap-0.5">
{[1,2,3,4,5].map((i) => (
<FiStar key={i} size={14} className="text-amber-400" style={{ fill: 'currentColor' }} />
))}
</div>
<span className="text-sm font-bold text-slate-900">4.9 / 5</span>
<span className="text-sm text-slate-500">note moyenne</span>
</div>
</motion.div>
</div>
</section>
)
}
+199
View File
@@ -0,0 +1,199 @@
export const mutuelles = [
{
id: 1,
rank: 1,
name: "Mon Santé",
initials: "MS",
tagline: "La référence 2026",
primaryColor: "#DC2626",
lightBg: "#FEF2F2",
badge: "Meilleur Choix 2026",
idealFor: ["Actifs", "Seniors"],
soins: 3,
dentaire: 2,
optique: 3,
hospitalization: "Élevée",
hospColor: "orange",
score: 9.8,
offer: null,
},
{
id: 2,
rank: 2,
name: "Identités Mutuelle",
initials: "ID",
tagline: "Idéal pour les actifs",
primaryColor: "#2563EB",
lightBg: "#EFF6FF",
badge: null,
idealFor: ["Actifs"],
soins: 3,
dentaire: 2,
optique: 3,
hospitalization: "Élevée",
hospColor: "orange",
score: 9.5,
offer: null,
},
{
id: 3,
rank: 3,
name: "Yeomi",
initials: "YE",
tagline: "Excellent rapport qualité-prix",
primaryColor: "#F97316",
lightBg: "#FFF7ED",
badge: null,
idealFor: ["Actifs", "Familles"],
soins: 3,
dentaire: 3,
optique: 2,
hospitalization: "Élevée",
hospColor: "orange",
score: 9.3,
offer: "3 MOIS OFFERTS",
},
{
id: 4,
rank: 4,
name: "Smatis",
initials: "SM",
tagline: "Excellence pour seniors",
primaryColor: "#7C3AED",
lightBg: "#F5F3FF",
badge: null,
idealFor: ["Seniors"],
soins: 3,
dentaire: 3,
optique: 3,
hospitalization: "Excellente",
hospColor: "green",
score: 9.1,
offer: null,
},
{
id: 5,
rank: 5,
name: "April",
initials: "AP",
tagline: "Flexibilité maximale",
primaryColor: "#0891B2",
lightBg: "#ECFEFF",
badge: null,
idealFor: ["Seniors", "Indépendants"],
soins: 2,
dentaire: 3,
optique: 3,
hospitalization: "Excellente",
hospColor: "green",
score: 8.9,
offer: null,
},
{
id: 6,
rank: 6,
name: "Helse",
initials: "HE",
tagline: "Rapport qualité-prix optimal",
primaryColor: "#059669",
lightBg: "#ECFDF5",
badge: null,
idealFor: ["Actifs", "Étudiants"],
soins: 2,
dentaire: 2,
optique: 3,
hospitalization: "Très bonne",
hospColor: "teal",
score: 8.7,
offer: null,
},
]
export const testimonials = [
{
id: 1,
name: "Fabienne Delat",
role: "Retraitée, 67 ans",
avatar: "FD",
avatarColor: "#7C3AED",
rating: 5,
text: "Je suis ravie des économies que j'ai pu réaliser en changeant de mutuelle grâce à Mon Gaston. Le comparateur est simple et les conseils très utiles. Je recommande à tous !",
},
{
id: 2,
name: "Nelly Skrzyniarz",
role: "Cadre, 41 ans",
avatar: "NS",
avatarColor: "#2563EB",
rating: 5,
text: "Mon conseiller était vraiment à l'écoute, sans pression. Il a pris le temps de comprendre mes besoins et m'a orienté vers la solution parfaite pour moi. Très satisfaite !",
},
{
id: 3,
name: "Joëlle Viot",
role: "Infirmière, 34 ans",
avatar: "JV",
avatarColor: "#DC2626",
rating: 5,
text: "J'ai un diabète et j'avais besoin de bons remboursements sur mes soins spécialisés. Mon Gaston m'a trouvé exactement ce qu'il fallait, rapidement et sans tracas.",
},
]
export const guides = [
"Pourquoi souscrire à une mutuelle santé ?",
"Quels sont les avantages de souscrire à une mutuelle santé ?",
"Quels sont les types de complémentaires santé ?",
"Comment bien choisir une mutuelle santé ?",
"Comment résilier sa mutuelle santé ?",
"Pourquoi choisir une mutuelle santé via un comparateur en ligne ?",
]
export const faqs = [
{
question: "Pourquoi souscrire à une mutuelle santé ?",
content: [
"La première mission de la mutuelle santé est d'indemniser la valeur du ticket modérateur — la partie non remboursée par la Sécurité sociale. Il est possible de classer les soins en 3 catégories selon leur niveau de remboursement.",
"Soins remboursés à 80 % : il s'agit par exemple des honoraires médicaux en cas d'hospitalisation. Une mutuelle couvre le reste des 20 % à votre charge.",
"Soins pris en charge à 70 % : consultations chez un médecin généraliste ou un spécialiste. La consultation coûte 25 €, la Sécu rembourse 16,50 € — la mutuelle couvre le solde.",
"Soins remboursés à 60 % : soins infirmiers, kinésithérapie et certains actes effectués en laboratoire.",
],
},
{
question: "Quels sont les avantages de souscrire à une mutuelle santé ?",
content: [
"La seconde mission d'une assurance santé est de rembourser les soins qui ne bénéficient d'aucun remboursement de la Sécurité sociale (actes hors AMO) : frais d'hospitalisation, séances d'ostéopathie, phytothérapie, médecines douces...",
"Accéder au tiers payant : vous n'avancez plus les frais médicaux. Un avantage considérable pour gérer votre budget de santé sereinement.",
"Bénéficier de services d'assistance : prise en charge en cas de difficultés liées à la santé — aide-ménagère, soutien scolaire, garde d'animaux, accompagnement. C'est un contrat central pour connaître les conditions.",
],
},
{
question: "Quels sont les types de complémentaire santé ?",
content: [
"Depuis le 1er janvier 2016, chaque employeur doit proposer une complémentaire santé collective à ses salariés — avec une prise en charge minimale de 50 % de la cotisation.",
"Si vous êtes salarié mais pas satisfait, ou si vous travaillez à votre compte, vous devrez souscrire une assurance santé individuelle. Elle est plus chère mais s'adapte entièrement à votre situation.",
],
},
{
question: "Comment bien choisir une mutuelle santé ?",
content: [
"Analysez vos besoins réels en matière de santé et ceux de vos ayants droit. Sans cette étape, vous risquez de payer trop cher ou d'être sous-couvert par rapport à vos dépenses de santé habituelles.",
"Soyez attentif aux exclusions de garanties : certains contrats ne prennent pas en charge les lunettes, les frais funéraires, la chirurgie esthétique, ou certains actes après une tentative de suicide.",
"Adaptez votre couverture à votre budget — les contrats de mutuelle sont conçus pour s'adapter à tous les profils, y compris les plus serrés.",
],
},
{
question: "Comment résilier sa mutuelle santé ?",
content: [
"Depuis le 1er décembre 2020, tout assuré peut résilier sa mutuelle santé à tout moment, à condition de bénéficier d'un contrat depuis plus d'un an. Par exemple, signé le 1er mars 2021, vous pouvez résilier à partir du 1er mars 2022, sans justification.",
"Il suffit d'envoyer une lettre de résiliation en recommandé. Et si vous souscrivez une nouvelle mutuelle, celle-ci peut se charger des démarches de résiliation à votre place.",
],
},
{
question: "Pourquoi choisir une mutuelle santé via un comparateur en ligne ?",
content: [
"Mon Gaston facilite les démarches des demandeurs de mutuelles santé en France. Face à la complexité de l'offre, il est difficile de choisir une mutuelle parfaitement adaptée à ses besoins et à son budget sans aide.",
"Pour vous aider à faire le tri dans cet océan d'offres, notre comparateur en ligne recense les meilleures offres de mutuelles santé. Cela permet de comparer sereinement sans perdre des heures.",
"Découvrez les formules Mon Gaston adaptées à votre profil — tirez parti de la concurrence pour bénéficier du meilleur rapport qualité-prix proposé.",
],
},
]
+10 -63
View File
@@ -1,68 +1,15 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
@import "tailwindcss";
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
@layer base {
html {
scroll-behavior: smooth;
}
a:hover {
color: #747bff;
body {
font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
button {
background-color: #f9f9f9;
* {
box-sizing: border-box;
}
}