feat(ui): modularisation du css et fidélisation des composants à la maquette figma

- découpage du css par composant (navbar, hero, footer, global)
- ajustement pixel-perfect du footer (newsletter full-bleed, alignements, typographies)
- documentation et commentaires détaillés du code react et css
This commit is contained in:
MohamedLoum1001
2026-09-23 17:27:05 +02:00
parent 517dc0711d
commit d3b9318dee
41 changed files with 11551 additions and 2101 deletions

7
.gitignore vendored
View File

@ -18,3 +18,10 @@
/public/assets/
/assets/vendor/
###< symfony/asset-mapper ###
###> symfony/webpack-encore-bundle ###
/node_modules/
/public/build/
npm-debug.log
yarn-error.log
###< symfony/webpack-encore-bundle ###

View File

@ -1,10 +1,10 @@
import './stimulus_bootstrap.js';
/*
* Welcome to your app's main JavaScript file!
*
* This file will be included onto the page via the importmap() Twig function,
* which should already be in your base.html.twig.
*/
import './styles/app.css';
import React from 'react';
import { createRoot } from 'react-dom/client';
import Home from './components/Home.jsx'; // <-- Ajoute .jsx ici !
console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');
const container = document.getElementById('root');
if (container) {
const root = createRoot(container);
root.render(<Home />);
}

View File

@ -0,0 +1,175 @@
import React, { useState } from 'react';
import bannerBg from '../img/homepageactuality.png';
const CATEGORIES = ['Tout', 'Actualités', 'Publications', 'Évènements', 'Projets'];
const NEWS_ITEMS = [
{ id: 1, tag: 'En ce moment' },
{ id: 2, tag: 'En ce moment' },
{ id: 3, tag: 'En ce moment' },
{ id: 4, tag: 'En ce moment' },
];
export default function ActualitySection() {
const [activeCategory, setActiveCategory] = useState('Tout');
const [email, setEmail] = useState('');
const handleSubmitNewsletter = (e) => {
e.preventDefault();
if (email) {
alert(`Inscription enregistrée pour : ${email}`);
setEmail('');
}
};
return (
<section className="w-full bg-white">
{/* 1. BANNIÈRE SUPÉRIEURE PLEINE LARGEUR */}
<div
className="relative w-full min-h-[460px] lg:min-h-[500px] bg-cover bg-right bg-no-repeat flex items-center overflow-hidden"
style={{ backgroundImage: `url("${bannerBg}")` }}
>
{/* Dégradé Figma : Blanc pur à gauche avec transition douce vers le paysage */}
<div
className="absolute inset-0 pointer-events-none"
style={{
background:
'linear-gradient(90deg, #ffffff 0%, #ffffff 38%, rgba(255,255,255,0.85) 48%, rgba(255,255,255,0) 68%)',
}}
/>
<div className="relative z-10 max-w-[1440px] w-full mx-auto px-6 sm:px-12 lg:px-16 py-12 flex flex-col lg:flex-row lg:items-center justify-between gap-10">
{/* Bloc Texte & CTA Gauche */}
<div className="max-w-md lg:max-w-lg shrink-0">
<span className="text-[0.72rem] font-bold tracking-[0.2em] text-[#8c8f3e] uppercase block mb-3">
ACTUALITÉ
</span>
<h2 className="text-3xl sm:text-4xl lg:text-[2.65rem] font-extrabold text-[#0b1320] leading-[1.18] tracking-tight mb-4">
Restez informé, <br />
Inspirer le changement
</h2>
<p className="text-xs sm:text-sm text-slate-500 mb-6 max-w-sm leading-relaxed">
Découvrez nos dernières actualités, publications, événements et initiatives sur le terrain
</p>
<a
href="#actualites"
className="inline-block bg-[#798334] hover:bg-[#656d2b] text-white text-xs sm:text-sm font-semibold px-6 py-2.5 rounded-full transition-colors shadow-sm"
>
Voir toute les actualité
</a>
</div>
{/* Carte Blanche Flottante "En ce moment" */}
<div className="bg-white rounded-3xl p-6 sm:p-8 shadow-[0_12px_36px_rgba(0,0,0,0.1)] border border-slate-100/60 w-full lg:max-w-[460px] h-[210px] sm:h-[230px] flex flex-col justify-between shrink-0">
<span className="text-xs font-semibold text-[#8c8f3e]">
En ce moment
</span>
<div className="text-slate-300 text-xs italic">
{/* Espace prévu pour le résumé de l'article à la une */}
</div>
<div className="h-2 w-14 bg-[#edf0db] rounded-full"></div>
</div>
</div>
</div>
{/* 2. ONGLETS DE FILTRES + GRILLE D'ACTUALITÉS */}
<div className="max-w-[1440px] mx-auto px-6 sm:px-12 lg:px-16 pt-10 pb-16">
{/* Menu épuré avec soulignement */}
<nav className="flex items-center gap-8 sm:gap-12 mb-10 overflow-x-auto no-scrollbar">
{CATEGORIES.map((cat) => {
const isActive = activeCategory === cat;
return (
<button
key={cat}
type="button"
onClick={() => setActiveCategory(cat)}
className={`text-sm tracking-tight transition-colors pb-1.5 relative whitespace-nowrap bg-transparent border-none cursor-pointer p-0 ${isActive
? 'text-slate-900 font-bold'
: 'text-slate-600 hover:text-slate-900 font-medium'
}`}
>
{cat}
{isActive && (
<span className="absolute bottom-0 left-0 w-full h-[2px] bg-slate-900 rounded-full" />
)}
</button>
);
})}
</nav>
{/* Grille des 4 cartes d'articles */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-16">
{NEWS_ITEMS.map((item) => (
<div
key={item.id}
className="bg-[#edf0db] rounded-2xl h-[300px] p-6 flex flex-col justify-between border border-[#e2e6cb] hover:shadow-md transition-shadow cursor-pointer"
>
<span className="text-xs font-semibold text-[#798334]">
{item.tag}
</span>
<div className="space-y-2.5">
<div className="h-3 w-4/5 bg-white/80 rounded-full"></div>
<div className="h-3 w-3/5 bg-white/80 rounded-full"></div>
</div>
</div>
))}
</div>
{/* 3. BANDEAU NEWSLETTER AUX COULEURS DE FIGMA */}
<div className="bg-[#C3A569] rounded-2xl p-6 sm:p-10 flex flex-col lg:flex-row items-center justify-between gap-6 shadow-sm">
{/* Icône enveloppe + Textes */}
<div className="flex items-center gap-6 max-w-xl">
{/* Cercle d'arrière-plan #D9D9D9 */}
<div className="w-16 h-16 rounded-full bg-[#D9D9D9] flex items-center justify-center shrink-0 shadow-inner">
{/* Icône violette #6568B9 */}
<svg
className="w-8 h-8 text-[#6568B9]"
fill="none"
stroke="currentColor"
strokeWidth="1.9"
strokeLinecap="round"
strokeLinejoin="round"
viewBox="0 0 24 24"
>
<rect x="3" y="5" width="18" height="14" rx="2" />
<polyline points="3 7 12 13 21 7" />
</svg>
</div>
<div>
<h3 className="text-lg sm:text-xl font-bold text-white leading-tight">
Ne manquez rien de nos actualités
</h3>
<p className="text-xs sm:text-sm text-white/90 mt-1 leading-relaxed">
Abonnez-vous à notre Newsletter pour recevoir nos dernières nouvelles et publications.
</p>
</div>
</div>
{/* Formulaire d'inscription */}
<form onSubmit={handleSubmitNewsletter} className="w-full lg:w-auto flex-1 max-w-md">
<div className="relative flex items-center">
<input
type="email"
required
placeholder="Votre adresse e-mail"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full bg-white/20 text-white placeholder-white/80 border border-white/50 rounded-full px-5 py-3 text-xs sm:text-sm focus:outline-none focus:ring-2 focus:ring-white pr-32"
/>
<button
type="submit"
className="absolute right-1.5 bg-[#8c8f3e] hover:bg-[#737630] text-white text-xs font-semibold px-5 py-2 rounded-full transition-colors shadow-sm"
>
S'abonner
</button>
</div>
<span className="text-[0.68rem] text-white/80 block mt-2 ml-4">
Nous respectons votre vie privée. Désinscription possible à tout moment.
</span>
</form>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,300 @@
import React from 'react';
import carreerBg from '../img/carreer.png';
export default function CareersSection() {
const parcours = [
{
label: 'Insertion professionnelle',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" />
</svg>
),
},
{
label: 'Développement de compétences',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H8V4h12v12z" />
</svg>
),
},
{
label: 'Leadership & coordination',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z" />
</svg>
),
},
{
label: 'Entrepreneuriat & innovation',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7z" />
</svg>
),
},
{
label: 'Recherche appliquée',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="none" stroke="currentColor" strokeWidth="2.2" viewBox="0 0 24 24">
<circle cx="11" cy="11" r="7" />
<path strokeLinecap="round" strokeLinejoin="round" d="m20 20-3.5-3.5" />
</svg>
),
},
{
label: 'Alumni & réseau',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 3 1 9l11 6 9-4.91V17h2V9L12 3z M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82z" />
</svg>
),
},
];
const competencesCol1 = [
{
label: 'Autonomie',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" />
</svg>
),
},
{
label: 'Gestion de projet',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" />
</svg>
),
},
{
label: 'Coordination',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<circle cx="9" cy="9" r="4" />
<path d="M9 15c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4zm7.76-9.64-1.41 1.41 1.41 1.41-1.41 1.42 1.41 1.41 1.42-1.41 1.41 1.41 1.41-1.41-1.41-1.42 1.41-1.41-1.41-1.41-1.42 1.41z" />
</svg>
),
},
{
label: 'Communication',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z" />
</svg>
),
},
{
label: 'Stratégie',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="none" stroke="currentColor" strokeWidth="2.2" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="9" />
<circle cx="12" cy="12" r="5" />
<circle cx="12" cy="12" r="1" />
</svg>
),
},
];
const competencesCol2 = [
{
label: 'Leadership',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
</svg>
),
},
{
label: 'Compétences opérationnelles',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="none" stroke="currentColor" strokeWidth="2.2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="m14.7 6.3 3 3-9.4 9.4H5.3v-3l9.4-9.4z" />
</svg>
),
},
{
label: 'Stabilité professionnelle',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 1 3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4z" />
</svg>
),
},
{
label: 'Capacité d’adaptation',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z" />
</svg>
),
},
{
label: 'Intelligence systémique',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z" />
</svg>
),
},
];
const opportunites = [
{
label: 'Stages',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 6h-4V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-6 0h-4V4h4v2z" />
</svg>
),
},
{
label: 'Mandats',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z" />
</svg>
),
},
{
label: 'Bénévolats',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="m12 21.35-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
</svg>
),
},
{
label: 'Collaborations',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z" />
</svg>
),
},
{
label: 'Projets entrepreneuriaux',
icon: (
<svg className="w-4 h-4 text-[#88ACE3]" fill="currentColor" viewBox="0 0 24 24">
<path d="M13.13 2.51c.37-.47.96-.71 1.57-.64.81.1 1.41.81 1.35 1.63L15.6 9h4.4c.73 0 1.35.52 1.48 1.24.13.72-.25 1.44-.9 1.74l-9 4.15c-.48.22-1.04.14-1.45-.2-.41-.34-.58-.88-.44-1.39L10.8 11H6.5c-.71 0-1.33-.5-1.47-1.2-.14-.7.22-1.41.86-1.72l7.24-5.57z" />
</svg>
),
},
];
return (
<section className="w-full bg-white pb-20">
{/* 1. BANNIÈRE SUPÉRIEURE PLEINE LARGEUR */}
<div
className="relative w-full min-h-[460px] lg:min-h-[500px] bg-cover bg-right bg-no-repeat flex items-center overflow-hidden"
style={{ backgroundImage: `url("${carreerBg}")` }}
>
{/* Dégradé Figma : Sombre à gauche avec fondu vers les personnes à droite */}
<div
className="absolute inset-0 pointer-events-none"
style={{
background:
'linear-gradient(90deg, #000000 0%, rgba(0,0,0,0.92) 34%, rgba(0,0,0,0.68) 52%, rgba(0,0,0,0) 75%)',
}}
/>
<div className="relative z-10 max-w-[1440px] w-full mx-auto px-6 sm:px-12 lg:px-16 py-12">
<div className="max-w-xl text-white">
<span className="text-[0.72rem] font-bold tracking-[0.2em] text-[#C3A569] uppercase block mb-3">
CAREERS & DEVELOPMENT
</span>
<h2 className="text-3xl sm:text-4xl lg:text-[2.65rem] font-extrabold leading-[1.15] tracking-tight mb-4">
Construire des trajectoires, <br />
pas seuement des emplois
</h2>
<p className="text-xs sm:text-sm text-slate-300 mb-2 max-w-lg leading-relaxed font-light">
Chez gaea21, les participants développent des compétences réelles à travers des projets réels, dans un environnement structuré, collaboratif et évolutif.
</p>
<p className="text-xs sm:text-sm text-slate-300 mb-8 max-w-lg leading-relaxed font-light">
L'objectif n'est pas un simple recrutement, mais la construction de trajectoires humaines et professionnelles durables.
</p>
<a
href="#parcours"
className="inline-flex items-center gap-2 bg-[#98984B] hover:bg-[#868641] text-white text-xs sm:text-sm font-semibold px-6 py-2.5 rounded-full transition-colors shadow-sm"
>
<span>Explorer les parcours</span>
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
<line x1="5" y1="12" x2="19" y2="12"></line>
<polyline points="12 5 19 12 12 19"></polyline>
</svg>
</a>
</div>
</div>
</div>
{/* 2. GRILLE DES 3 CARTES DU BAS (BEIGE #F1F0E0) */}
<div className="max-w-[1440px] mx-auto px-6 sm:px-12 lg:px-16 pt-10">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 items-stretch">
{/* CARTE 1 : Parcours disponibles */}
<div className="lg:col-span-4 bg-[#F1F0E0] rounded-2xl p-6 sm:p-8 flex flex-col justify-start">
<h3 className="text-xs font-semibold text-slate-700 mb-5">
Parcours disponibles
</h3>
<ul className="space-y-3.5">
{parcours.map((item, idx) => (
<li key={idx} className="flex items-center gap-3 text-xs sm:text-[0.82rem] text-slate-800 font-medium">
<span className="shrink-0">{item.icon}</span>
<span>{item.label}</span>
</li>
))}
</ul>
</div>
{/* CARTE 2 : Ce que les participants développent */}
<div className="lg:col-span-5 bg-[#F1F0E0] rounded-2xl p-6 sm:p-8 flex flex-col justify-start">
<h3 className="text-xs font-semibold text-slate-700 mb-5">
Ce que les participants développent
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-3.5">
<ul className="space-y-3.5">
{competencesCol1.map((item, idx) => (
<li key={idx} className="flex items-center gap-3 text-xs sm:text-[0.82rem] text-slate-800 font-medium">
<span className="shrink-0">{item.icon}</span>
<span>{item.label}</span>
</li>
))}
</ul>
<ul className="space-y-3.5">
{competencesCol2.map((item, idx) => (
<li key={idx} className="flex items-center gap-3 text-xs sm:text-[0.82rem] text-slate-800 font-medium">
<span className="shrink-0">{item.icon}</span>
<span>{item.label}</span>
</li>
))}
</ul>
</div>
</div>
{/* CARTE 3 : Opportunités */}
<div className="lg:col-span-3 bg-[#F1F0E0] rounded-2xl p-6 sm:p-8 flex flex-col justify-start">
<h3 className="text-xs font-semibold text-slate-700 mb-5">
Opportunités
</h3>
<ul className="space-y-3.5">
{opportunites.map((item, idx) => (
<li key={idx} className="flex items-center gap-3 text-xs sm:text-[0.82rem] text-slate-800 font-medium">
<span className="shrink-0">{item.icon}</span>
<span>{item.label}</span>
</li>
))}
</ul>
</div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,166 @@
import React, { useState } from 'react';
// Importation du logo vectoriel ou transparent de gaea21
import logo from '../img/logo_gaea21.png';
// Importation de la feuille de style dédiée au Footer
import '../styles/footer.css';
export default function Footer() {
// État local pour stocker l'adresse email saisie par l'utilisateur
const [email, setEmail] = useState('');
// Gestionnaire de soumission du formulaire d'abonnement à la newsletter
const handleSubmitNewsletter = (e) => {
e.preventDefault();
if (email) {
alert(`Inscription enregistrée pour : ${email}`);
setEmail('');
}
};
// Liste ordonnée des liens pour la colonne "Écosystème"
const ecosystemLinks = [
'Individus',
'Entreprises',
'Institutions',
'Universités',
'Praticiens',
];
return (
<footer className="gaea-footer">
{/* -------------------------------------------------------------------
1. SECTION SUPÉRIEURE (Centrée sur 1440px)
Comprend : Le logo + Réseaux sociaux, le trait doré et les colonnes
------------------------------------------------------------------- */}
<div className="gaea-footer-inner">
<div className="footer-top-row">
{/* Bloc Marque : Logo gaea21 et liens sociaux */}
<div className="footer-brand-col">
<a href="/" className="footer-logo">
<img src={logo} alt="Logo gaea21" />
</a>
<div className="footer-socials">
{/* Icône LinkedIn */}
<a href="#linkedin" aria-label="LinkedIn">
<svg viewBox="0 0 24 24">
<path d="M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14m-.5 15.5v-5.3a3.26 3.26 0 0 0-3.26-3.26c-.85 0-1.84.52-2.28 1.3v-1.11h-2.79v8.37h2.79v-4.93c0-.77.62-1.4 1.39-1.4a1.4 1.4 0 0 1 1.4 1.4v4.93h2.75M6.88 8.56a1.68 1.68 0 0 0 1.68-1.68c0-.93-.75-1.69-1.68-1.69a1.69 1.69 0 0 0-1.69 1.69c0 .93.76 1.68 1.69 1.68m1.39 9.94v-8.37H5.5v8.37h2.77z" />
</svg>
</a>
{/* Icône Instagram */}
<a href="#instagram" aria-label="Instagram">
<svg viewBox="0 0 24 24">
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.13-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z" />
</svg>
</a>
{/* Icône YouTube */}
<a href="#youtube" aria-label="YouTube">
<svg viewBox="0 0 24 24">
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
</svg>
</a>
</div>
</div>
{/* Ligne verticale dorée (#A8985A) de séparation */}
<div className="footer-gold-divider" />
{/* Les 3 colonnes de liens de navigation */}
<div className="footer-nav-columns">
{/* Colonne 1 : Écosystème */}
<div>
<h4 className="footer-col-title">Écosystème</h4>
<ul className="footer-links-list">
{ecosystemLinks.map((item, idx) => (
<li key={idx}>
<a href={`#${item.toLowerCase()}`}>{item}</a>
</li>
))}
</ul>
</div>
{/* Colonne 2 : Ressources */}
<div>
<h4 className="footer-col-title">Ressources</h4>
</div>
{/* Colonne 3 : À propos */}
<div>
<h4 className="footer-col-title">À propos</h4>
</div>
</div>
</div>
</div>
{/* -------------------------------------------------------------------
2. BANDEAU NEWSLETTER (Pleine Largeur / Full bleed)
S'étend d'un bord à l'autre de l'écran avec la couleur #D8DCAB
------------------------------------------------------------------- */}
<div className="footer-newsletter-fullwidth">
<div className="footer-newsletter-content">
{/* Côté gauche : Pastille ronde grise avec icône violette + Textes */}
<div className="newsletter-brand-side">
<div className="newsletter-icon-circle">
<svg fill="none" stroke="currentColor" strokeWidth="1.8" viewBox="0 0 24 24">
<rect x="3" y="5" width="18" height="14" rx="2" />
<polyline points="3 7 12 13 21 7" />
</svg>
</div>
<div className="newsletter-texts">
<h3>Ne manquez rien de nos actualités</h3>
<p>
Abonnez vous a notre Newsletters pour recevoir <br />
nos dernières nouvelles et publications
</p>
</div>
</div>
{/* Côté droit : Formulaire (Champ ovale noir + Bouton vert olive) */}
<form onSubmit={handleSubmitNewsletter} className="newsletter-form-side">
<div className="newsletter-input-group">
<input
type="email"
required
placeholder="Votre adresse e-mail"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="newsletter-input"
/>
<button type="submit" className="newsletter-btn">
S'abonner
</button>
</div>
<span className="newsletter-privacy-text">
Nous respectons votre vie privée. Désinscription possible à tout moment
</span>
</form>
</div>
</div>
{/* -------------------------------------------------------------------
3. BARRE DE MENTIONS LÉGALES TOUT EN BAS (Centrée sur 1440px)
------------------------------------------------------------------- */}
<div className="gaea-footer-inner">
<div className="footer-bottom-bar">
<div>gaea21 2026 - Tout droit réservés</div>
<div className="footer-bottom-links">
<a href="#mentions-legales">Mentions légales</a>
<span className="divider">|</span>
<a href="#confidentialite">Politiques de confidentialités</a>
<span className="divider">|</span>
<a href="#cookies">Cookies</a>
</div>
</div>
</div>
</footer>
);
}

View File

@ -0,0 +1,119 @@
// 1. IMPORTATION DES MODULES ET ASSETS
import React, { useState } from 'react';
// Import du logo gaea21 via Webpack Encore (gestion du hashing et du chemin public)
import logo from '../img/logo_gaea21.png';
/**
* 2. COMPOSANT PRINCIPAL : Header
*
* Rôle fonctionnel :
* - Fournit la barre de navigation principale (Navbar) fixée ou relative en haut de page.
* - Gère l'affichage responsive : menu étendu sur desktop, tiroir repliable (drawer) sur mobile.
* - Propose un bouton d'action principal (« Participer ») pour convertir le visiteur.
*/
export default function Header() {
/**
* ÉTAT LOCAL (State) : isOpen
* - false : le menu mobile est fermé
* - true : le menu mobile est ouvert (déroulé/visible)
*/
const [isOpen, setIsOpen] = useState(false);
return (
/*
Balise sémantique HTML5 <header> :
- navbar-container : gère le fond, la bordure inférieure éventuelle,
la hauteur fixe et le z-index pour rester au-dessus du contenu de la page.
*/
<header className="navbar-container">
{/*
Conteneur interne centré :
- navbar-content : applique un flexbox (justify-content: space-between, align-items: center)
et borne la largeur maximale avec un padding latéral propre.
*/}
<div className="navbar-content">
{/* =========================================================
A. LOGO DE LA MARQUE
========================================================= */}
<a href="/" className="navbar-logo" aria-label="Retour à l'accueil gaea21">
<img src={logo} alt="Logo gaea21" />
</a>
{/* =========================================================
B. BOUTON BURGER (Affiché uniquement sur écrans mobiles)
========================================================= */}
<button
type="button"
/*
Basculement dynamique de la classe 'active' :
Permet en CSS d'animer les 3 barres pour former une croix 'X' quand isOpen vaut true.
*/
className={`hamburger ${isOpen ? 'active' : ''}`}
/* Inverse la valeur booléenne au clic (ouvre ou ferme le menu) */
onClick={() => setIsOpen(!isOpen)}
/* Attributs d'accessibilité ARIA pour les lecteurs d'écran */
aria-label={isOpen ? 'Fermer le menu de navigation' : 'Ouvrir le menu de navigation'}
aria-expanded={isOpen}
>
{/* Les 3 barres horizontales du burger dessinées en CSS */}
<span></span>
<span></span>
<span></span>
</button>
{/* =========================================================
C. MENU DE NAVIGATION (DESKTOP & TIROIR MOBILE)
========================================================= */}
{/*
La classe 'open' est ajoutée conditionnellement pour déclencher
l'apparition du tiroir de navigation en version mobile.
*/}
<nav className={`navbar-nav ${isOpen ? 'open' : ''}`}>
{/* Liste ordonnée des ancres de navigation */}
<ul className="nav-links">
<li>
{/* Le onClick={() => setIsOpen(false)} referme automatiquement le menu mobile après le clic */}
<a href="#parcours" onClick={() => setIsOpen(false)}>
Parcours
</a>
</li>
<li>
<a href="#projet" onClick={() => setIsOpen(false)}>
Projet
</a>
</li>
<li>
<a href="#ecosysteme" onClick={() => setIsOpen(false)}>
Écosystème
</a>
</li>
<li>
<a href="#contact" onClick={() => setIsOpen(false)}>
Contact
</a>
</li>
</ul>
{/* =========================================================
D. BOUTON D'ACTION PRINCIPAL (CALL-TO-ACTION)
========================================================= */}
<div className="navbar-action">
<a
href="#participer"
className="btn-participer"
// Ferme le menu mobile si cliqué
onClick={() => setIsOpen(false)}
>
Participer
</a>
</div>
</nav>
</div>
</header>
);
}

104
assets/components/Hero.jsx Normal file
View File

@ -0,0 +1,104 @@
// 1. IMPORTATIONS DES MODULES ET DES ASSETS
import React from 'react';
// Import de l'image de bannière principale via Webpack Encore
// Webpack génère automatiquement le bon chemin d'accès avec hachage pour le cache
import heroBg from '../img/hero-banner.png';
/**
* 2. COMPOSANT PRINCIPAL : Hero
*
* Rôle fonctionnel et visuel :
* - Première section visible sous le Header (zone "Above the Fold").
* - Transmet la proposition de valeur principale de gaea21 via un titre H1 percutant.
* - Propose un bouton d'action (Call-to-Action) guidant l'utilisateur vers la découverte de l'écosystème.
*/
export default function Hero() {
return (
/*
SECTION ENGOBANTE DU HERO :
- className="hero-section" : applique les dimensions (hauteur minimale/100vh),
le positionnement d'arrière-plan (background-size: cover, background-position: center)
et le positionnement relatif pour caler les calques enfants.
- style={{ backgroundImage: ... }} : injection dynamique de l'URL de l'image
traitée par le bundler Webpack.
*/
<section
className="hero-section"
style={{ backgroundImage: `url("${heroBg}")` }}
>
{/*
CALQUE D'OVERLAY SOMBRE :
- Élément positionné en absolu couvrant toute la surface de la section.
- Applique un gradient ou un filtre semi-transparent sombre afin d'assurer
un contraste suffisant (normes d'accessibilité WCAG) entre le texte blanc
et les zones claires de l'image d'arrière-plan.
*/}
<div className="hero-overlay"></div>
{/*
CONTENEUR DE CONTENU CENTRÉ :
- Assure le centrage horizontal du bloc de texte sur la largeur maximale de la page.
- z-index supérieur à l'overlay pour que les textes et boutons restent cliquables et visibles.
*/}
<div className="hero-content">
{/*
BLOC TEXTUEL :
- Limite la largeur maximale du texte pour éviter qu'il ne s'étale trop sur grands écrans,
ce qui garantit un confort de lecture optimal.
*/}
<div className="hero-text-block">
{/*
TITRE PRINCIPAL H1 :
- Balise unique et essentielle pour le référencement naturel (SEO).
- Les balises <br /> forcent des retours à la ligne précis pour sculpter
la silhouette du texte exactement comme sur la maquette Figma.
*/}
<h1 className="hero-title">
Construire <br />
les infrastructures <br />
humaines, économiques <br />
et territoriales de demain
</h1>
{/*
PARAGRAPHE D'INTRODUCTION :
- Résume succinctement les axes d'impact et la légitimité historique (20 ans) de gaea21.
*/}
<p className="hero-description">
Depuis plus de 20 ans, gaea21 relie insertion professionnelle, santé mentale,
économie locale, transition durable, recherche appliquée et innovation systémique.
</p>
{/*
BOUTON D'APPEL À L'ACTION (CTA) :
- Lien d'ancrage vers la section "#ecosysteme".
- Composé d'un label textuel et d'une flèche SVG vectorielle indiquant l'action/la direction.
*/}
<a href="#ecosysteme" className="btn-hero">
<span>Explorer l'écosystème</span>
{/* Flèche droite indicative (alignée horizontalement avec le texte) */}
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
// Masque l'icône purement décorative des lecteurs d'écran
aria-hidden="true"
>
<line x1="5" y1="12" x2="19" y2="12"></line>
<polyline points="12 5 19 12 12 19"></polyline>
</svg>
</a>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,82 @@
// 1. IMPORTATION DES DÉPENDANCES ET COMPOSANTS ENFANTS
import React from 'react';
// Barre de navigation supérieure (Logo, Liens, Boutons d'action)
import Header from './Header';
// Section d'accroche principale (Titre fort, boutons d'appel à l'action, visuel héroïque)
import Hero from './Hero';
// Cartes des profils cibles ("Qui êtes-vous ?") : Individus, Entreprises, Institutions, etc.
import ProfileCards from './ProfileCards';
// Section explicative de la méthodologie ("Comment ça fonctionne ?") en 6 étapes + chiffres clés
import Methodology from './Methodology';
// Section de preuve d'impact chiffrée avec fond immersif et carrousel
import Impact from './Impact';
// Section actualités : événements, articles, publications et formulaire de newsletter intermédiaire
import ActualitySection from './ActualitySection';
// Section opportunités et carrières ("Construire des trajectoires, pas seulement des emplois")
import CareersSection from './CareersSection';
// Pied de page complet (Navigation secondaire, réseaux sociaux, newsletter pleine largeur, mentions légales)
import Footer from './Footer';
/**
* 2. COMPOSANT RACINE DE LA PAGE D'ACCUEIL : Home
*
* Rôle architectural :
* - Sert de page principale (Page Component / Container) orchestrant la disposition verticale
* de toutes les sections de la page d'accueil de gaea21.
* - Assure une hiérarchie sémantique HTML5 claire pour le référencement (SEO) et l'accessibilité.
*/
export default function Home() {
return (
/*
CONTENEUR GLOBAL DE PAGE :
- relative : sert de référentiel de positionnement pour d'éventuels éléments absolus.
- min-h-screen : garantit que la page couvre au minimum 100% de la hauteur de la fenêtre (évite les fonds blancs coupés).
- bg-white : couleur de fond par défaut pour l'ensemble du site.
- overflow-x-hidden : masque tout débordement horizontal causé par des animations, des bannières pleine largeur ou des marges négatives.
*/
<div className="relative min-h-screen bg-white overflow-x-hidden">
{/*
A. EN-TÊTE DU SITE
Balise <Header /> contenant la navigation principale fixée ou relative en haut de page.
*/}
<Header />
{/*
B. CONTENU PRINCIPAL DE LA PAGE
La balise sémantique HTML5 <main> délimite le cœur informationnel unique de la page d'accueil.
*/}
<main>
{/* Bannière d'introduction */}
<Hero />
{/* Sélection des parcours utilisateurs */}
<ProfileCards />
{/* Présentation du modèle "Learning by Doing" */}
<Methodology />
{/* Statistiques d'impact et résultats sur 20 ans */}
<Impact />
{/* Actualités, publications et veille */}
<ActualitySection />
{/* Opportunités professionnelles et développement de compétences */}
<CareersSection />
{/* Pied de page et liens institutionnels */}
<Footer />
</main>
</div>
);
}

View File

@ -0,0 +1,180 @@
// 1. IMPORTATIONS DES MODULES ET ASSETS
import React, { useState } from 'react';
// Webpack Encore gère l'empreinte de hachage (hash) et le chemin public de l'image de fond
import impactBg from '../img/impact.png';
/**
* 2. STRUCTURE DE DONNÉES STATIQUE : DIAPOSITIVES DU CARROUSEL
* Externaliser les données hors du composant évite de recréer ce tableau
* en mémoire à chaque nouveau cycle de rendu (re-render) du composant.
*
* Le tableau est structuré sous forme de tableau de diapositives (slides).
* SLIDES[0] contient le groupe actif affiché à l'écran.
*/
const SLIDES = [
[
{
value: '3000 +',
label: 'personnes accompagnées',
size: 'w-16 h-16 sm:w-20 sm:h-20', // Taille standard des pastilles latérales
},
{
value: '94% - 97%',
label: 'de réussites en insertion',
size: 'w-24 h-24 sm:w-28 sm:h-28', // Pastille centrale plus imposante pour créer une hiérarchie visuelle
},
{
value: '20 ANS',
label: 'de recherches appliquées',
size: 'w-16 h-16 sm:w-20 sm:h-20', // Taille standard
},
],
];
/**
* 3. COMPOSANT PRINCIPAL : Impact
* Présente la section "NOTRE IMPACT" avec :
* - Un arrière-plan immersif en pleine largeur
* - 3 indicateurs chiffrés majeurs avec pastilles colorées
* - Des contrôles de carrousel (flèches gauche/droite et indicateurs à points)
*/
export default function Impact() {
/**
* ÉTAT LOCAL (State) : Indice du point actif de pagination
* Initialisé à 2 pour correspondre au point central parmi les 5 points du design Figma.
*/
const [currentSlide, setCurrentSlide] = useState(2);
return (
/*
Balise sémantique <section> :
- relative : point d'ancrage pour positionner l'overlay sombre absolu
- w-full : occupe toute la largeur de l'écran (full bleed)
- py-24 sm:py-32 : padding vertical généreux pour donner de l'espace à la section
- px-6 sm:px-12 : padding horizontal adaptatif
- bg-cover bg-center : l'image remplit tout l'arrière-plan sans déformation et reste centrée
- overflow-hidden : empêche tout débordement lié aux animations de zoom
*/
<section
className="relative w-full py-24 sm:py-32 px-6 sm:px-12 bg-cover bg-center overflow-hidden"
style={{ backgroundImage: `url("${impactBg}")` }}
>
{/*
CALQUE D'OVERLAY SOMBRE :
- absolute inset-0 : couvre 100% de la surface de la section
- bg-black/40 : voile noir à 40% d'opacité pour garantir la lisibilité des textes blancs
- pointer-events-none : empêche ce filtre de capturer les clics
*/}
<div className="absolute inset-0 bg-black/40 pointer-events-none" />
{/*
CONTENEUR DE CONTENU :
- relative z-10 : positionne le contenu au-dessus du voile sombre
- max-w-[1400px] mx-auto : largeur maximale cadrée et centrée
- text-center text-white : typographie blanche centrée par défaut
*/}
<div className="relative z-10 max-w-[1400px] mx-auto text-center text-white">
{/* ====================================================================
A. EN-TÊTE : SURTITRE & TITRE PRINCIPAL
==================================================================== */}
{/* Surtitre en majuscules avec espacement étendu (tracking-wide) */}
<span className="text-xs sm:text-sm font-bold tracking-[0.25em] uppercase text-white/90 block mb-2">
NOTRE IMPACT
</span>
{/* Titre descriptif sobre avec marge basse importante avant les chiffres */}
<h2 className="text-lg sm:text-xl font-normal text-white/95 mb-16 sm:mb-20">
Des résultats concrets et durables
</h2>
{/* ====================================================================
B. ZONE CENTRALE : FLÈCHE GAUCHE + 3 MÉTRIQUES + FLÈCHE DROITE
- flex items-center justify-between : alignement horizontal sur une seule ligne
==================================================================== */}
<div className="flex items-center justify-between gap-4 sm:gap-8">
{/* Bouton de navigation gauche (Précédent) */}
<button
type="button"
className="w-10 h-10 sm:w-12 sm:h-12 rounded-full border border-white/60 flex items-center justify-center text-white hover:bg-white/20 transition-colors shrink-0"
aria-label="Précédent"
>
{/* Icône flèche retour */}
<svg className="w-5 h-5 sm:w-6 sm:h-6" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
</svg>
</button>
{/*
Grille des 3 métriques chiffrées :
- 1 colonne sur mobile (grid-cols-1)
- 3 colonnes à partir des écrans moyens (md:grid-cols-3)
- items-end : aligne les éléments par le bas pour absorber la différence de taille du cercle central
- flex-1 max-w-4xl mx-auto : occupe l'espace central disponible de façon équilibrée
*/}
<div className="grid grid-cols-1 md:grid-cols-3 gap-10 md:gap-8 flex-1 items-end max-w-4xl mx-auto">
{SLIDES[0].map((item, index) => (
<div key={index} className="flex flex-col items-center">
{/*
Pastille ronde vert olive (#9da74d) :
- Taille dynamique issue de la donnée (item.size)
- shadow-[...] : ombre portée sombre accentuant la profondeur
- hover:scale-105 : micro-interaction au survol
*/}
<div
className={`${item.size} rounded-full bg-[#9da74d] mb-6 shadow-[0_4px_16px_rgba(0,0,0,0.35)] transition-transform duration-300 hover:scale-105`}
/>
{/* Valeur numérique mise en avant (ex: 3000 +, 94% - 97%) */}
<span className="text-3xl sm:text-4xl md:text-5xl font-normal tracking-tight text-white mb-2">
{item.value}
</span>
{/* Libellé descriptif sous le chiffre */}
<span className="text-xs sm:text-sm font-light text-white/85 max-w-[200px]">
{item.label}
</span>
</div>
))}
</div>
{/* Bouton de navigation droit (Suivant) */}
<button
type="button"
className="w-10 h-10 sm:w-12 sm:h-12 rounded-full border border-white/60 flex items-center justify-center text-white hover:bg-white/20 transition-colors shrink-0"
aria-label="Suivant"
>
{/* Icône flèche suivant */}
<svg className="w-5 h-5 sm:w-6 sm:h-6" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
</svg>
</button>
</div>
{/* ====================================================================
C. INDICATEURS DE PAGINATION (CAROUSEL DOTS)
- 5 points horizontaux centrés en bas de section
==================================================================== */}
<div className="flex justify-center items-center gap-2 mt-16 sm:mt-20">
{[0, 1, 2, 3, 4].map((dot) => (
<span
key={dot}
/*
Style dynamique du point :
- Si c'est le point actif (dot === currentSlide) : couleur vert olive (#a3ad53) et taille augmentée (scale-125)
- Sinon : blanc translucide (bg-white/50)
*/
className={`w-2.5 h-2.5 rounded-full transition-all duration-200 ${dot === currentSlide
? 'bg-[#a3ad53] scale-125'
: 'bg-white/50'
}`}
/>
))}
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,259 @@
// 1. IMPORTATION DE REACT
import React from 'react';
/**
* 2. TABLEAU DE DONNÉES STATIQUE : LES 6 ÉTAPES DE LA MÉTHODOLOGIE
* Externaliser les étapes hors du composant évite de recréer ce tableau
* en mémoire à chaque nouveau cycle de rendu (re-render) du composant.
*/
const STEPS = [
{
number: '01',
title: 'Diagnostiquer',
description: 'Comprendre les besoins humains, professionnels et systémiques.',
// Icône SVG : Loupe d'analyse / audit
icon: (
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" strokeWidth="2.2" viewBox="0 0 24 24">
<circle cx="11" cy="11" r="7" />
<path strokeLinecap="round" strokeLinejoin="round" d="m20 20-3.5-3.5" />
</svg>
),
},
{
number: '02',
title: 'Structurer',
description: 'Créer un cadre, des outils et des trajectoires cohérentes.',
// Icône SVG : Arborescence / Organigramme hiérarchique
icon: (
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" strokeWidth="2.2" viewBox="0 0 24 24">
<rect x="9" y="3" width="6" height="5" rx="1" />
<rect x="3" y="16" width="6" height="5" rx="1" />
<rect x="15" y="16" width="6" height="5" rx="1" />
<path strokeLinecap="round" strokeLinejoin="round" d="M12 8v4m0 0H6v4m6-4h6v4" />
</svg>
),
},
{
number: '03',
title: 'Appliquer',
description: 'Travailler sur des projets réels dans un environnement collaboratif.',
// Icône SVG : Outil clé / mise en œuvre pratique
icon: (
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" strokeWidth="2.2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="m14.7 6.3 3 3-9.4 9.4H5.3v-3l9.4-9.4z" />
<path strokeLinecap="round" strokeLinejoin="round" d="m13 8 3 3" />
</svg>
),
},
{
number: '04',
title: 'Transformer',
description: 'Développer autonomie, compétences et stabilité.',
// Icône SVG : Pousse végétale / croissance durable
icon: (
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" strokeWidth="2.2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 21V11" />
<path strokeLinecap="round" strokeLinejoin="round" d="M12 11c0-4 4-7 8-7-1 4-4 7-8 7z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M12 13c0-3.5-3.5-6-7-6 .9 3.5 3.5 6 7 6z" />
</svg>
),
},
{
number: '05',
title: 'Connecter',
description: 'Relier humain, économie, territoires et opportunités.',
// Icône SVG : Maillons de chaîne / réseau
icon: (
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" strokeWidth="2.2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path strokeLinecap="round" strokeLinejoin="round" d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
),
},
{
number: '06',
title: 'Régénérer',
description: 'Créer des boucles de valeur autonomes et durables.',
// Icône SVG : Flèches d'économie circulaire / régénération
icon: (
<svg className="w-7 h-7 text-white" fill="none" stroke="currentColor" strokeWidth="2.2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M21 12a9 9 0 0 0-15.5-6.36L3 8" />
<path strokeLinecap="round" strokeLinejoin="round" d="M3 3v5h5" />
<path strokeLinecap="round" strokeLinejoin="round" d="M3 12a9 9 0 0 0 15.5 6.36L21 16" />
<path strokeLinecap="round" strokeLinejoin="round" d="M16 16h5v5" />
</svg>
),
},
];
/**
* 3. COMPOSANT PRINCIPAL : Methodology
* Présente la section "Comment ça fonctionne ?" en deux parties :
* - Haut : Titre + descriptif à gauche (4 cols) et les 6 étapes à droite (8 cols).
* - Bas : Bandeau beige avec statistiques clés et bouton d'action.
*/
export default function Methodology() {
return (
/*
Section englobante :
- py-20 : padding vertical de 5rem (80px)
- px-6 sm:px-12 : padding horizontal adaptatif
- bg-white : fond blanc assurant la continuité avec les autres blocs
*/
<section className="py-20 px-6 sm:px-12 bg-white">
{/* Conteneur centré borné à 1400px (largeur standard de la maquette Figma) */}
<div className="max-w-[1400px] mx-auto">
{/* ====================================================================
A. PARTIE SUPÉRIEURE : EN-TÊTE + GRILLE DES 6 ÉTAPES
- Grille CSS de 12 colonnes sur grand écran (lg:grid-cols-12)
- 1 colonne unique sur mobile / tablette
==================================================================== */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-10 items-start mb-16">
{/* COLONNE GAUCHE (4 colonnes sur 12) : Titres et texte explicatif */}
<div className="lg:col-span-4 pr-0 lg:pr-6">
{/* Surtitre vert olive (#798334) */}
<span className="text-[0.85rem] font-bold text-[#798334] tracking-wide block mb-2">
Notre méthodologie
</span>
{/* Titre principal */}
<h2 className="text-3xl sm:text-4xl font-extrabold text-slate-900 leading-tight mb-5">
Comment <br /> ça fonctionne ?
</h2>
{/* Sous-titre mis en avant */}
<h3 className="text-sm font-bold text-slate-900 mb-3">
Une méthodologie “Learning by Doing” validée sur plus de 20 ans.
</h3>
{/* Paragraphe descriptif avec largeur bridée pour une lecture confortable */}
<p className="text-xs sm:text-sm text-slate-500 leading-relaxed max-w-sm">
gaea21 transforme la recherche en action et les trajectoires humaines en capacités concrètes à travers une méthodologie systémique structurée.
</p>
</div>
{/* COLONNE DROITE (8 colonnes sur 12) : Les 6 étapes numérotées */}
{/*
Découpage responsive :
- Mobile : 2 colonnes (grid-cols-2)
- Tablette : 3 colonnes (sm:grid-cols-3)
- Desktop : 6 colonnes alignées (md:grid-cols-6)
*/}
<div className="lg:col-span-8 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-6 text-center">
{STEPS.map((step) => (
/*
group : permet à la bulle d'icône d'animer son zoom et sa couleur
dès que le curseur entre dans le bloc de l'étape
*/
<div key={step.number} className="flex flex-col items-center group">
{/* Numéro de l'étape (01, 02, etc.) */}
<span className="text-xs sm:text-sm font-semibold text-slate-400 mb-2">
{step.number}
</span>
{/* Bulle d'icône vert olive (#8c8f3e) avec transition fluide */}
<div className="w-16 h-16 rounded-full bg-[#8c8f3e] flex items-center justify-center mb-3 shadow-md group-hover:bg-[#737630] group-hover:scale-105 transition-all duration-200">
{step.icon}
</div>
{/* Titre de l'étape */}
<h4 className="text-sm font-bold text-slate-900 mb-1.5 leading-snug">
{step.title}
</h4>
{/* Description de l'étape */}
<p className="text-[0.68rem] sm:text-[0.72rem] text-slate-500 leading-snug">
{step.description}
</p>
</div>
))}
</div>
</div>
{/* ====================================================================
B. BANDEAU INFÉRIEUR : STATISTIQUES & BOUTON D'ACTION (CTA)
- bg-[#f7f8f0] : fond beige/vert très clair doux
- border-[#e8ebd5] : bordure fine d'encadrement
- rounded-2xl : coins arrondis
==================================================================== */}
<div className="bg-[#f7f8f0] border border-[#e8ebd5] rounded-2xl p-6 sm:p-8 flex flex-col lg:flex-row items-center justify-between gap-6 shadow-sm">
{/* Bloc Accroche textuelle à gauche */}
<div className="lg:w-1/4 text-center lg:text-left">
<h4 className="text-sm sm:text-base font-bold text-slate-900 leading-snug">
Une approche systémique et humaine
</h4>
<span className="text-xs sm:text-sm text-[#798334] font-medium block mt-0.5">
pour des résultats concrets et durables.
</span>
</div>
{/* Ligne séparatrice verticale visible uniquement sur desktop (lg) */}
<div className="hidden lg:block w-px h-12 bg-slate-300"></div>
{/* Statistique 1 : Ancienneté (20+ ans) */}
<div className="flex items-center gap-4">
{/* Icône cible / horloge */}
<div className="text-[#8c8f3e]">
<svg className="w-8 h-8" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="9" />
<path strokeLinecap="round" strokeLinejoin="round" d="M12 7v5l3 3" />
</svg>
</div>
<div>
<span className="text-xl sm:text-2xl font-black text-slate-900 block leading-tight">
20 + ans
</span>
<span className="text-xs text-slate-500 font-medium">
d'expérimentation
</span>
</div>
</div>
{/* Ligne séparatrice verticale visible uniquement sur desktop (lg) */}
<div className="hidden lg:block w-px h-12 bg-slate-300"></div>
{/* Statistique 2 : Nombre de bénéficiaires (3 000+ personnes) */}
<div className="flex items-center gap-4">
{/* Icône groupe / communauté */}
<div className="text-[#8c8f3e]">
<svg className="w-8 h-8" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path strokeLinecap="round" strokeLinejoin="round" d="M22 21v-2a4 4 0 0 0-3-3.87" />
<path strokeLinecap="round" strokeLinejoin="round" d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
</div>
<div>
<span className="text-xl sm:text-2xl font-black text-slate-900 block leading-tight">
3 000 +
</span>
<span className="text-xs text-[#798334] font-medium">
personnes accompagnées
</span>
</div>
</div>
{/* Bouton CTA d'ancrage vers le détail de la méthodologie */}
<div className="mt-2 lg:mt-0">
<a
href="#methodologie"
className="inline-flex items-center gap-2 bg-[#798334] hover:bg-[#656d2b] text-white text-xs sm:text-sm font-semibold px-6 py-3 rounded-full transition-all duration-200 shadow-sm hover:shadow-md transform hover:-translate-y-0.5"
>
<span>Découvrir la méthodologie</span>
{/* Flèche droite indicative */}
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
<line x1="5" y1="12" x2="19" y2="12"></line>
<polyline points="12 5 19 12 12 19"></polyline>
</svg>
</a>
</div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,147 @@
// 1. IMPORTATIONS DES MODULES ET DES ASSETS
import React from 'react';
// Webpack Encore gère l'empreinte de hachage (hashing) et le chemin public des images
import individusImg from '../img/individus.png';
import entrepriseImg from '../img/entreprise.png';
import institutionsImg from '../img/institutions.png';
import coachImg from '../img/coach.png';
/**
* 2. STRUCTURE DE DONNÉES STATIQUE : LISTE DES PROFILS CIBLES
* Externaliser les données hors du composant évite de recréer
* ce tableau en mémoire à chaque nouveau rendu (re-render) de React.
*/
const PROFILES = [
{
id: 1,
title: 'Individus',
image: individusImg,
href: '#individus', // Ancre ou future route vers le parcours Individus
},
{
id: 2,
title: 'Entreprises',
image: entrepriseImg,
href: '#entreprises',
},
{
id: 3,
title: 'Institutions',
image: institutionsImg,
href: '#institutions',
},
{
id: 4,
// Le caractère d'échappement '\n' force un saut de ligne contrôlé dans le titre
title: 'Université /\nCentre de recherche',
// Fallback d'image en attendant l'asset spécifique de l'université
image: institutionsImg,
href: '#universite',
},
{
id: 5,
title: 'Coach ou\nPraticien',
image: coachImg,
href: '#coach',
},
];
/**
* 3. COMPOSANT PRINCIPAL : ProfileCards
* Affiche la section de choix de profil utilisateur (« Qui êtes-vous ? »)
* sous forme d'une grille interactive de cartes verticales avec effet glass/hover.
*/
export default function ProfileCards() {
return (
/*
Balise sémantique <section> :
- py-20 : grand espacement vertical (padding-top & padding-bottom)
- px-6 sm:px-12 : padding horizontal adaptatif selon la taille d'écran
- bg-white : fond blanc assurant la continuité visuelle
*/
<section className="py-20 px-6 sm:px-12 bg-white">
{/*
Conteneur centré borné à 1380px max (conforme au Figma)
mx-auto permet de centrer horizontalement le conteneur dans la page
*/}
<div className="max-w-[1380px] mx-auto text-center">
{/* =========================================================
A. EN-TÊTE DE SECTION (Surtitre + Titre principal)
========================================================= */}
{/* Surtitre en majuscules avec espacement étendu entre les lettres (tracking) */}
<span className="text-[0.72rem] tracking-[0.22em] font-bold text-slate-400 uppercase block mb-3">
QUI ÊTES-VOUS ?
</span>
{/* Titre principal centré avec largeur maximale contrôlée (max-w-xl) */}
<h2 className="text-xl sm:text-2xl md:text-3xl font-semibold text-slate-800 leading-snug max-w-xl mx-auto mb-14">
Chaque profil est une porte d’entrée <br />
vers un écosystème de transformation
</h2>
{/* =========================================================
B. GRILLE RESPONSIVE DES 5 CARTES
- 2 colonnes sur mobile (grid-cols-2)
- 3 colonnes sur tablette (sm:grid-cols-3)
- 5 colonnes alignées sur desktop (md:grid-cols-5)
- Gouttières (gap) dynamiques
========================================================= */}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3.5 sm:gap-4 lg:gap-5 justify-center">
{PROFILES.map((profile) => (
/*
Chaque carte est un lien interactif cliquable <a> :
- key={profile.id} : clé unique obligatoire pour l'algorithme de réconciliation de React
- group : permet aux éléments enfants (image, overlay) de réagir au survol du parent
- relative + overflow-hidden : indispensable pour contraindre l'image zoomée à l'arrondi (rounded-xl)
- h-[...] : hauteurs fixes échelonnées par breakpoint pour garder un ratio portrait élancé
- transform hover:-translate-y-1.5 : translation douce vers le haut au survol pour donner du relief
*/
<a
key={profile.id}
href={profile.href}
className="group relative block w-full h-[360px] sm:h-[420px] md:h-[460px] rounded-xl overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1.5"
>
{/*
CALQUE 1 (Fond) : Photographie du profil
- object-cover : l'image remplit tout le conteneur sans se déformer
- group-hover:scale-105 : léger zoom cinématographique quand la souris survole la carte
- transition-transform duration-500 : animation fluide du zoom
*/}
<img
src={profile.image}
alt={profile.title.replace('\n', ' ')} // Remplace le saut de ligne par un espace pour l'accessibilité SEO
className="w-full h-full object-cover object-center group-hover:scale-105 transition-transform duration-500 ease-out"
/>
{/*
CALQUE 2 (Intermédiaire) : Dégradé sombre pour la lisibilité
- bg-gradient-to-t : dégradé vertical partant du bas vers le haut
- from-black/85 via-black/30 to-transparent : foncé en bas pour le texte, transparent en haut
- pointer-events-none : empêche ce calque décoratif de bloquer les clics
*/}
<div className="absolute inset-0 bg-gradient-to-t from-black/85 via-black/30 to-transparent pointer-events-none" />
{/*
CALQUE 3 (Premier plan) : Titre textuel de la carte
- absolute inset-x-0 bottom-0 : positionné en bas de la carte sur toute la largeur
- flex items-end justify-center : aligne le texte en bas et au centre
*/}
<div className="absolute inset-x-0 bottom-0 p-4 pb-6 flex items-end justify-center text-center">
{/*
- whitespace-pre-line : interprète les retours à la ligne '\n' définis dans le tableau PROFILES
- drop-shadow-md : ombre portée sur le texte pour garantir un contraste optimal
*/}
<span className="text-white text-base sm:text-lg font-bold leading-snug whitespace-pre-line tracking-tight drop-shadow-md">
{profile.title}
</span>
</div>
</a>
))}
</div>
</div>
</section>
);
}

BIN
assets/img/carreer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
assets/img/coach.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

BIN
assets/img/entreprise.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

BIN
assets/img/hero-banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
assets/img/impact.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
assets/img/individus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

BIN
assets/img/institutions.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

BIN
assets/img/logo_gaea21.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
assets/img/univeriste.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

View File

@ -1,3 +1,248 @@
body {
background-color: skyblue;
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* NAVBAR */
.navbar-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: 50;
padding: 1.5rem 3rem;
box-sizing: border-box;
}
.navbar-content {
max-width: 1440px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
}
.navbar-logo img {
height: 46px;
width: auto;
display: block;
object-fit: contain;
}
.navbar-nav {
display: flex;
align-items: center;
gap: 4rem;
}
.nav-links {
display: flex;
list-style: none;
gap: 3rem;
margin: 0;
padding: 0;
}
.nav-links li a {
text-decoration: none;
color: #1e293b;
font-size: 0.95rem;
font-weight: 500;
/* Contour lumineux pour détacher les liens du feuillage */
text-shadow: 0 1px 3px rgba(255, 255, 255, 0.8), 0 0 12px rgba(255, 255, 255, 0.6);
transition: color 0.2s ease;
}
.nav-links li a:hover {
color: #8c8f3e;
}
.btn-participer {
background-color: #8c8f3e;
color: #ffffff;
padding: 0.65rem 1.75rem;
border-radius: 9999px;
font-size: 0.95rem;
font-weight: 500;
text-decoration: none;
display: inline-block;
transition: background-color 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
}
.btn-participer:hover {
background-color: #737630;
transform: translateY(-1px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.18);
}
.hamburger {
display: none;
flex-direction: column;
justify-content: space-around;
width: 28px;
height: 22px;
background: transparent;
border: none;
cursor: pointer;
z-index: 60;
padding: 0;
}
.hamburger span {
width: 100%;
height: 2.5px;
background-color: #1e293b;
border-radius: 2px;
transition: all 0.3s ease;
}
/* HERO BANNER */
.hero-section {
position: relative;
width: 100%;
min-height: 100vh;
background-size: cover;
background-position: center right;
background-repeat: no-repeat;
display: flex;
align-items: center;
padding-top: 5rem;
}
.hero-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg,
rgba(255, 255, 255, 0.45) 0%,
rgba(255, 255, 255, 0.15) 50%,
rgba(255, 255, 255, 0) 100%);
z-index: 1;
}
.hero-content {
position: relative;
z-index: 2;
max-width: 1380px;
width: 100%;
margin: 0 auto;
padding: 0 2rem;
}
.hero-text-block {
max-width: 580px;
}
.hero-title {
font-size: 2.75rem;
font-weight: 800;
line-height: 1.2;
color: #0b1320;
margin-bottom: 1.25rem;
letter-spacing: -0.02em;
}
.hero-description {
font-size: 0.88rem;
line-height: 1.55;
color: #724213;
margin-bottom: 2rem;
max-width: 460px;
font-weight: 500;
}
.btn-hero {
display: inline-flex;
align-items: center;
gap: 0.6rem;
background-color: #8c8f3e;
color: #ffffff;
padding: 0.75rem 1.6rem;
border-radius: 9999px;
font-size: 0.95rem;
font-weight: 600;
text-decoration: none;
transition: all 0.25s ease;
box-shadow: 0 4px 12px rgba(140, 143, 62, 0.25);
}
.btn-hero:hover {
background-color: #737630;
transform: translateY(-2px);
}
/* RESPONSIVE */
@media (max-width: 960px) {
.navbar-container {
padding: 1.25rem 1.5rem;
}
.hamburger {
display: flex;
}
.hamburger.active span:nth-child(1) {
transform: rotate(45deg) translate(5px, 5px);
}
.hamburger.active span:nth-child(2) {
opacity: 0;
}
.hamburger.active span:nth-child(3) {
transform: rotate(-45deg) translate(6px, -6px);
}
.navbar-nav {
position: fixed;
top: 0;
right: -100%;
width: 280px;
height: 100vh;
background-color: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(12px);
flex-direction: column;
justify-content: center;
gap: 2rem;
box-shadow: -4px 0 20px rgba(0, 0, 0, 0.08);
transition: right 0.35s ease-in-out;
}
.navbar-nav.open {
right: 0;
}
.nav-links {
flex-direction: column;
align-items: center;
gap: 1.75rem;
}
.nav-links li a {
text-shadow: none;
font-size: 1.05rem;
}
.hero-section {
min-height: auto;
padding: 7rem 0 4rem 0;
background-position: 70% center;
}
.hero-overlay {
background: rgba(255, 255, 255, 0.75);
}
.hero-title {
font-size: 1.85rem;
}
}

367
assets/styles/footer.css Normal file
View File

@ -0,0 +1,367 @@
/* ==========================================================================
FOOTER GAEA21
========================================================================== */
/*
Conteneur global du footer :
- Fond blanc pur (#ffffff).
- Padding haut généreux pour aérer par rapport à la section précédente.
- overflow-x caché pour éviter tout scroll horizontal indésirable.
*/
.gaea-footer {
width: 100%;
background-color: #ffffff;
padding-top: 50px;
box-sizing: border-box;
overflow-x: hidden;
}
/*
Conteneur interne pour centrer le contenu :
- Calé sur la largeur maximale de la maquette (1440px).
- Marges automatiques gauche/droite pour un centrage parfait.
- Padding latéral de sécurité pour les écrans plus étroits.
*/
.gaea-footer-inner {
max-width: 1440px;
margin: 0 auto;
padding: 0 48px;
box-sizing: border-box;
}
/* ==========================================================================
1. PARTIE SUPÉRIEURE : LOGO | LIGNE SÉPARATRICE | 3 COLONNES
========================================================================== */
.footer-top-row {
display: flex;
align-items: flex-start;
/* Aligne le haut du logo avec le haut des titres de colonnes */
padding-bottom: 60px;
/* Espace entre la navigation et le bandeau newsletter */
width: 100%;
box-sizing: border-box;
}
/* Bloc Marque : Logo gaea21 et icônes des réseaux sociaux */
.footer-brand-col {
display: flex;
flex-direction: column;
justify-content: flex-start;
width: 210px;
flex-shrink: 0;
/* Empêche le logo de rétrécir */
}
.footer-logo {
display: block;
margin: 0 0 24px 0;
/* Espacement entre le logo et les icônes */
line-height: 1;
}
.footer-logo img {
height: 48px;
width: auto;
display: block;
object-fit: contain;
}
/* Rangée des icônes de réseaux sociaux */
.footer-socials {
display: flex;
align-items: center;
gap: 20px;
/* Espace régulier entre chaque icône */
}
.footer-socials a {
color: #000000;
display: inline-flex;
transition: opacity 0.2s ease;
}
.footer-socials a:hover {
opacity: 0.7;
/* Effet au survol */
}
.footer-socials svg {
width: 20px;
height: 20px;
fill: currentColor;
}
/*
Séparateur vertical doré Figma :
- Couleur exacte dorée / bronze (#A8985A).
- Hauteur proportionnelle à la zone logo + liens.
- Marges précises pour équilibrer le vide visuel.
*/
.footer-gold-divider {
width: 1.5px;
height: 130px;
background-color: #A8985A;
margin: 0 65px 0 45px;
/* 45px à gauche (côté logo), 65px à droite (côté liens) */
flex-shrink: 0;
align-self: flex-start;
}
/*
Zone des colonnes de navigation :
- Distribution équilibrée des colonnes avec un gap généreux.
- padding-top léger (2px) pour aligner exactement la ligne de base du titre « Écosystème » avec le haut du logo.
*/
.footer-nav-columns {
display: flex;
gap: 150px;
/* Espacement net entre « Écosystème », « Ressources », « À propos » */
flex: 1;
padding-top: 2px;
}
/* Titres dorés/kaki des colonnes de navigation */
.footer-col-title {
color: #A8985A;
font-size: 14.5px;
font-weight: 700;
margin: 0 0 18px 0;
letter-spacing: -0.01em;
line-height: 1;
}
/* Liste des liens */
.footer-links-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 10px;
/* Espace vertical entre chaque sous-lien */
}
.footer-links-list li a {
color: #1a1a1a;
font-size: 13px;
text-decoration: none;
font-weight: 400;
line-height: 1.35;
transition: color 0.15s ease;
}
.footer-links-list li a:hover {
color: #000000;
}
/* ==========================================================================
2. BANDEAU NEWSLETTER (PLEINE LARGEUR TOTALE - FULL BLEED)
========================================================================== */
.footer-newsletter-fullwidth {
width: 100%;
background-color: #D8DCAB;
/* Teinte officielle verte amande/kaki clair du Figma */
padding: 28px 0;
box-sizing: border-box;
}
/* Conteneur intérieur du bandeau newsletter */
.footer-newsletter-content {
max-width: 1440px;
margin: 0 auto;
padding: 0 48px;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
}
/* Partie gauche : Disque icône + Titre & descriptif */
.newsletter-brand-side {
display: flex;
align-items: center;
gap: 22px;
}
/* Cercle gris clair (#DDE1EA) sous l'icône enveloppe */
.newsletter-icon-circle {
width: 64px;
height: 64px;
border-radius: 50%;
background-color: #DDE1EA;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
/* Enveloppe violette (#6568B9) */
.newsletter-icon-circle svg {
width: 32px;
height: 32px;
color: #6568B9;
}
.newsletter-texts h3 {
font-size: 20px;
font-weight: 800;
color: #000000;
margin: 0 0 4px 0;
line-height: 1.2;
letter-spacing: -0.01em;
}
.newsletter-texts p {
font-size: 12.5px;
color: #000000;
margin: 0;
line-height: 1.4;
font-weight: 400;
}
/* Partie droite : Formulaire d'abonnement */
.newsletter-form-side {
display: flex;
flex-direction: column;
align-items: flex-start;
flex-shrink: 0;
}
.newsletter-input-group {
display: flex;
align-items: center;
gap: 16px;
}
/* Champ de saisie ovale bordé de noir */
.newsletter-input {
width: 320px;
height: 44px;
background-color: transparent;
border: 1.5px solid #000000;
border-radius: 9999px;
padding: 0 24px;
font-size: 13px;
color: #000000;
outline: none;
box-sizing: border-box;
}
.newsletter-input::placeholder {
color: #222222;
opacity: 0.85;
}
/* Bouton vert olive (#A4AC56 / #98984B) */
.newsletter-btn {
height: 44px;
padding: 0 28px;
background-color: #A4AC56;
color: #ffffff;
font-size: 13px;
font-weight: 600;
border-radius: 9999px;
border: none;
cursor: pointer;
flex-shrink: 0;
transition: opacity 0.2s ease, transform 0.1s ease;
}
.newsletter-btn:hover {
opacity: 0.92;
}
.newsletter-btn:active {
transform: scale(0.98);
}
.newsletter-privacy-text {
font-size: 10.5px;
color: #000000;
margin-top: 6px;
padding-left: 10px;
font-weight: 400;
}
/* ==========================================================================
3. BARRE DE MENTIONS LÉGALES TOUT EN BAS
========================================================================== */
.footer-bottom-bar {
padding-top: 18px;
padding-bottom: 24px;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 11px;
color: #444444;
}
.footer-bottom-links {
display: flex;
align-items: center;
gap: 16px;
}
.footer-bottom-links a {
color: inherit;
text-decoration: none;
transition: color 0.15s ease;
}
.footer-bottom-links a:hover {
color: #000000;
}
.footer-bottom-links .divider {
color: #BCA86A;
/* Couleur dorée pour le séparateur vertical */
}
/* ==========================================================================
RESPONSIVE : ADAPTATION SUR ÉCRANS MOBILES ET TABLETTES
========================================================================== */
@media (max-width: 1024px) {
.gaea-footer-inner,
.footer-newsletter-content {
padding: 0 24px;
}
.footer-top-row {
flex-direction: column;
gap: 35px;
padding-bottom: 40px;
}
.footer-gold-divider {
display: none;
/* Masqué sur petit écran pour aérer */
}
.footer-nav-columns {
gap: 40px;
flex-wrap: wrap;
padding-top: 0;
}
.footer-newsletter-content {
flex-direction: column;
align-items: flex-start;
gap: 22px;
}
.newsletter-input-group {
width: 100%;
flex-wrap: wrap;
}
.newsletter-input {
width: 100%;
}
.footer-bottom-bar {
flex-direction: column;
gap: 12px;
text-align: center;
}
}

0
assets/styles/header.css Normal file
View File

0
assets/styles/hero.css Normal file
View File

View File

@ -41,7 +41,9 @@
"symfony/ux-turbo": "^2.36",
"symfony/validator": "7.4.*",
"symfony/web-link": "7.4.*",
"symfony/webpack-encore-bundle": "^2.4",
"symfony/yaml": "7.4.*",
"symfonycasts/tailwind-bundle": "^1.0",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0"
},

4301
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -13,4 +13,6 @@ return [
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Symfonycasts\TailwindBundle\SymfonycastsTailwindBundle::class => ['all' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
];

View File

@ -0,0 +1,2 @@
symfonycasts_tailwind:
binary_version: v3.4.19

View File

@ -0,0 +1,45 @@
webpack_encore:
# The path where Encore is building the assets - i.e. Encore.setOutputPath()
output_path: '%kernel.project_dir%/public/build'
# If multiple builds are defined (as shown below), you can disable the default build:
# output_path: false
# Set attributes that will be rendered on all script and link tags
script_attributes:
defer: true
# Uncomment (also under link_attributes) if using Turbo Drive
# https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change
# 'data-turbo-track': reload
# link_attributes:
# Uncomment if using Turbo Drive
# 'data-turbo-track': reload
# If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
# crossorigin: 'anonymous'
# Preload all rendered script and link tags automatically via the HTTP/2 Link header
# preload: true
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
# strict_mode: false
# If you have multiple builds:
# builds:
# frontend: '%kernel.project_dir%/public/frontend/build'
# pass the build name as the 3rd argument to the Twig functions
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
framework:
assets:
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
#when@prod:
# webpack_encore:
# # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
# # Available in version 1.2
# cache: true
#when@test:
# webpack_encore:
# strict_mode: false

View File

@ -1526,6 +1526,26 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* generate_final_classes?: bool|Param, // Default: true
* generate_final_entities?: bool|Param, // Default: false
* }
* @psalm-type SymfonycastsTailwindConfig = array{
* input_css?: list<scalar|Param|null>,
* config_file?: scalar|Param|null, // Path to the tailwind.config.js file // Default: "%kernel.project_dir%/tailwind.config.js"
* binary?: scalar|Param|null, // The tailwind binary to use instead of downloading a new one // Default: null
* binary_version?: scalar|Param|null, // Tailwind CLI version to download - required unless "binary" is set (run "tailwind:init" to configure) // Default: null
* binary_platform?: "auto"|"linux-arm64"|"linux-arm64-musl"|"linux-x64"|"linux-x64-musl"|"macos-arm64"|"macos-x64"|"windows-x64"|Param, // Tailwind CLI platform to download - "auto" will try to detect the platform automatically // Default: "auto"
* postcss_config_file?: scalar|Param|null, // Path to PostCSS config file which is passed to the Tailwind CLI // Default: null
* strict_mode?: bool|Param|null, // When enabled, an exception will be thrown if there are no built assets (default: false in `test` env, true otherwise) // Default: null
* process_timeout?: int|Param, // Timeout in seconds for the Tailwind build process - use "0" to disable // Default: 60
* }
* @psalm-type WebpackEncoreConfig = array{
* output_path?: scalar|Param|null, // The path where Encore is building the assets - i.e. Encore.setOutputPath()
* crossorigin?: false|"anonymous"|"use-credentials"|Param, // crossorigin value when Encore.enableIntegrityHashes() is used, can be false (default), anonymous or use-credentials // Default: false
* preload?: bool|Param, // preload all rendered script and link tags automatically via the http2 Link header. // Default: false
* cache?: bool|Param, // Enable caching of the entry point file(s) // Default: false
* strict_mode?: bool|Param, // Throw an exception if the entrypoints.json file is missing or an entry is missing from the data // Default: true
* builds?: array<string, scalar|Param|null>,
* script_attributes?: array<string, scalar|Param|null>,
* link_attributes?: array<string, scalar|Param|null>,
* }
* @psalm-type ConfigType = array{
* imports?: ImportsConfig,
* parameters?: ParametersConfig,
@ -1539,6 +1559,8 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* twig_extra?: TwigExtraConfig,
* security?: SecurityConfig,
* monolog?: MonologConfig,
* symfonycasts_tailwind?: SymfonycastsTailwindConfig,
* webpack_encore?: WebpackEncoreConfig,
* "when@dev"?: array{
* imports?: ImportsConfig,
* parameters?: ParametersConfig,
@ -1555,6 +1577,8 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* security?: SecurityConfig,
* monolog?: MonologConfig,
* maker?: MakerConfig,
* symfonycasts_tailwind?: SymfonycastsTailwindConfig,
* webpack_encore?: WebpackEncoreConfig,
* },
* "when@prod"?: array{
* imports?: ImportsConfig,
@ -1569,6 +1593,8 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* twig_extra?: TwigExtraConfig,
* security?: SecurityConfig,
* monolog?: MonologConfig,
* symfonycasts_tailwind?: SymfonycastsTailwindConfig,
* webpack_encore?: WebpackEncoreConfig,
* },
* "when@test"?: array{
* imports?: ImportsConfig,
@ -1584,6 +1610,8 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
* twig_extra?: TwigExtraConfig,
* security?: SecurityConfig,
* monolog?: MonologConfig,
* symfonycasts_tailwind?: SymfonycastsTailwindConfig,
* webpack_encore?: WebpackEncoreConfig,
* },
* ...<string, ExtensionType|array{ // extra keys must follow the when@%env% pattern or match an extension alias
* imports?: ImportsConfig,

View File

@ -25,4 +25,10 @@ return [
'@hotwired/turbo' => [
'version' => '8.0.23',
],
'react' => [
'version' => '19.3.0',
],
'react-dom' => [
'version' => '19.3.0',
],
];

6942
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
package.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "gaea21-v2-web",
"type": "module",
"private": true,
"license": "UNLICENSED",
"scripts": {
"dev-server": "encore dev-server",
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production --progress"
},
"devDependencies": {
"@babel/core": "^7.24.0",
"@babel/preset-env": "^7.24.0",
"@babel/preset-react": "^7.24.0",
"@symfony/webpack-encore": "^5.1.0",
"autoprefixer": "^10.6.1",
"babel-plugin-polyfill-corejs3": "^1.0.0",
"core-js": "^3.40.0",
"postcss-loader": "^8.1.0",
"tailwindcss": "^3.4.0",
"webpack": "^5.90.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
}

6
postcss.config.cjs Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@ -0,0 +1,16 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class HomeController extends AbstractController
{
#[Route('/', name: 'app_home')]
public function index(): Response
{
return $this->render('home/index.html.twig');
}
}

View File

@ -319,6 +319,31 @@
"./config/packages/messenger.yaml"
]
},
"symfony/webpack-encore-bundle": {
"version": "2.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.2",
"ref": "b618bf5f17ed61f523da7153025bf58ec9502f65"
},
"files": [
"./assets/app.js",
"./assets/styles/app.css",
"./config/packages/webpack_encore.yaml",
"./package.json",
"./webpack.config.js"
]
},
"symfonycasts/tailwind-bundle": {
"version": "1.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "0.14",
"ref": "b8af60cec7149fd0cde0ec9d89ccb32335c4d9bd"
}
},
"twig/extra-bundle": {
"version": "v3.29.0"
}

23
tailwind.config.js Normal file
View File

@ -0,0 +1,23 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./assets/**/*.{js,jsx,ts,tsx}",
"./templates/**/*.html.twig",
],
theme: {
extend: {
colors: {
gaea: {
olive: '#8c8f3e',
'olive-hover': '#737630',
dark: '#0b1320',
brown: '#724213',
}
},
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
}
},
},
plugins: [],
}

View File

@ -2,21 +2,18 @@
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}gaea21 - Accueil{% endblock %}</title>
{# Fichiers CSS compilés par Webpack Encore #}
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
{# Fichiers JS compilés par Webpack Encore #}
{% block javascripts %}
{% block importmap %}{{ importmap('app') }}{% endblock %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
{% set frankenphp_hot_reload = app.request.server.get('FRANKENPHP_HOT_RELOAD') %}
{% if frankenphp_hot_reload %}
<meta name="frankenphp-hot-reload:url" content="{{ frankenphp_hot_reload }}">
<script src="https://cdn.jsdelivr.net/npm/idiomorph"></script>
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
{% endif %}
</head>
<body>
{% block body %}{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html.twig' %}
{% block title %}gaea21 - Accueil{% endblock %}
{% block body %}
<div id="root"></div>
{% endblock %}

30
webpack.config.cjs Normal file
View File

@ -0,0 +1,30 @@
const Encore = require('@symfony/webpack-encore');
// Manually configure the runtime environment if not already configured yet by the "encore" command.
if (!Encore.isRuntimeEnvironmentConfigured()) {
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}
Encore
// Répertoire où les fichiers compilés seront stockés
.setOutputPath('public/build/')
// Chemin public utilisé par le serveur web
.setPublicPath('/build')
/*
* ENTRY CONFIG
*/
.addEntry('app', './assets/app.js')
.splitEntryChunks()
.enableSingleRuntimeChunk()
.cleanupOutputBeforeBuild()
.enableSourceMaps(!Encore.isProduction())
.enableVersioning(Encore.isProduction())
// Activer React
.enableReactPreset()
.enablePostCssLoader()
;
module.exports = Encore.getWebpackConfig();