update landing page

This commit is contained in:
Thomas Fransolet 2026-02-27 13:10:22 +01:00
parent 9dd74c4853
commit 75b8e6b7b8

View File

@ -258,10 +258,10 @@ export default function Home() {
fullDesc: "Déploiement de la technologie MyInfoMate sur les bornes interactives de l'Office du Tourisme de Namur. Cette installation permet aux visiteurs d'explorer la ville de manière tactile, avec des points d'intérêt géo-localisés et une planification d'itinéraire simplifiée.", fullDesc: "Déploiement de la technologie MyInfoMate sur les bornes interactives de l'Office du Tourisme de Namur. Cette installation permet aux visiteurs d'explorer la ville de manière tactile, avec des points d'intérêt géo-localisés et une planification d'itinéraire simplifiée.",
images: [ images: [
resolveImage("PORTFOLIO_NAMUR"), resolveImage("PORTFOLIO_NAMUR"),
"https://images.unsplash.com/photo-1517048676732-d65bc937f952?q=80&w=2070", "https://images.unsplash.com/photo-1551434678-e076c223a692?q=80&w=2070",
"https://images.unsplash.com/photo-1531403001884-48a690c749ea?q=80&w=2070" "https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?q=80&w=2070"
], ],
offset: true, offset: false,
}, },
{ {
title: "Carnaval de Marche", title: "Carnaval de Marche",
@ -279,22 +279,22 @@ export default function Home() {
{ {
title: "Fort de Saint-Héribert", title: "Fort de Saint-Héribert",
categories: ["Mobile", "Embedded", "Web"], categories: ["Mobile", "Embedded", "Web"],
desc: "Application mobile immersive pour la visite du fort via QR codes et localisation. Finalisée en mai 2023.", desc: "Application mobile immersive pour la visite du fort via QR codes et localisation.",
img: resolveImage("PORTFOLIO_HERIBERT"), img: resolveImage("PORTFOLIO_HERIBERT"),
fullDesc: "Notre réalisation pour le Fort de Saint-Héribert consiste en une application mobile dédiée à la visite du fort. Cette application offre une expérience immersive, où le contenu peut être consulté soit en scannant un QR code, soit en activant l'affichage de contenu basé sur la localisation du visiteur. La détection de proximité permet une interaction fluide avec le patrimoine.", fullDesc: "Notre réalisation pour le Fort de Saint-Héribert consiste en une application mobile dédiée à la visite du fort. Cette application offre une expérience immersive, où le contenu peut être consulté soit en scannant un QR code, soit en activant l'affichage de contenu basé sur la localisation du visiteur.",
images: [ images: [
resolveImage("PORTFOLIO_HERIBERT"), resolveImage("PORTFOLIO_HERIBERT"),
"https://images.unsplash.com/photo-1533106418989-88406c7cc8ca?q=80&w=2070", "https://images.unsplash.com/photo-1533106418989-88406c7cc8ca?q=80&w=2070",
"https://images.unsplash.com/photo-1496307653780-42ee777d4833?q=80&w=2070" "https://images.unsplash.com/photo-1496307653780-42ee777d4833?q=80&w=2070"
], ],
offset: true, offset: false,
}, },
{ {
title: "Musée de la fraise", title: "Musée de la fraise",
categories: ["Desktop", "Mobile"], categories: ["Desktop", "Mobile"],
desc: "Tablettes interactives et gestionnaire de contenu pour enrichir l'expérience musée.", desc: "Tablettes interactives et gestionnaire de contenu pour enrichir l'expérience musée.",
img: resolveImage("PORTFOLIO_FRAISE"), img: resolveImage("PORTFOLIO_FRAISE"),
fullDesc: "Le projet pour le musée de la fraise de Wépion avait pour objectif d'intégrer des tablettes interactives. Nous avons créé un gestionnaire dédié (MyMuseum) permettant au personnel de personnaliser le contenu, ainsi que l'application mobile chargée sur les tablettes pour la consultation des visiteurs.", fullDesc: "Le projet pour le musée de la fraise de Wépion avait pour objectif d'intégrer des tablettes interactives. Nous avons créé un gestionnaire dédié (MyMuseum) permettant au personnel de personnaliser le contenu.",
images: [ images: [
resolveImage("PORTFOLIO_FRAISE"), resolveImage("PORTFOLIO_FRAISE"),
"https://images.unsplash.com/photo-1581291518062-c07a09ea0937?q=80&w=2070", "https://images.unsplash.com/photo-1581291518062-c07a09ea0937?q=80&w=2070",
@ -307,54 +307,51 @@ export default function Home() {
categories: ["Web"], categories: ["Web"],
desc: "Site vitrine et boutique en ligne pour investir dans une coopérative locale.", desc: "Site vitrine et boutique en ligne pour investir dans une coopérative locale.",
img: resolveImage("PORTFOLIO_CHISTREE"), img: resolveImage("PORTFOLIO_CHISTREE"),
fullDesc: "La coopérative souhaitait un site internet vitrine permettant aux visiteurs d'investir dans ce beau projet via une petite boutique en ligne intégrée. Une solution Web complète alliant esthétique et fonctionnalité e-commerce.", fullDesc: "La coopérative souhaitait un site internet vitrine permettant aux visiteurs d'investir dans ce beau projet via une petite boutique en ligne intégrée.",
images: [ images: [
resolveImage("PORTFOLIO_CHISTREE"), resolveImage("PORTFOLIO_CHISTREE"),
"https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?q=80&w=2070", "https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?q=80&w=2070",
"https://images.unsplash.com/photo-1460925895917-afdab827c52f?q=80&w=2015" "https://images.unsplash.com/photo-1460925895917-afdab827c52f?q=80&w=2015"
], ],
offset: true, offset: false,
}, },
]; ];
const initialProjects = allProjects.slice(0, 4); const firstProjects = allProjects.slice(0, 4);
const extraProjects = allProjects.slice(4); const extraProjects = allProjects.slice(4);
return ( return (
<div className="flex flex-col gap-16 relative"> <div className="relative">
{/* Initial 4 projects */} {/* Projects Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-16"> <div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-16">
{initialProjects.map((project, idx) => ( {firstProjects.map((project, idx) => (
<div <div
key={idx} key={idx}
className={`group relative overflow-hidden rounded-[3rem] aspect-[4/3] cursor-pointer shadow-2xl ${project.offset ? "md:translate-y-20" : "" className="group relative overflow-hidden rounded-[3rem] aspect-[4/3] cursor-pointer shadow-2xl"
}`} onClick={() => {
setSelectedProject(project);
setCurrentImageIndex(0);
}}
> >
<img <img
src={project.img} src={project.img}
alt={project.title} alt={project.title}
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110" className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110"
/> />
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 flex flex-col justify-end p-12"> <div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 flex flex-col justify-end p-10 md:p-12">
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-primary font-bold text-xs uppercase tracking-widest mb-1"> <span className="text-primary font-bold text-xs uppercase tracking-widest mb-1">
{project.categories.join(" / ")} {project.categories.join(" / ")}
</span> </span>
<h3 className="text-white text-2xl font-black mb-4"> <h3 className="text-white text-2xl md:text-3xl font-black mb-4">
{project.title} {project.title}
</h3> </h3>
</div> </div>
<p className="text-white/60 mb-8 text-sm leading-relaxed line-clamp-2"> <p className="text-white/60 mb-8 text-sm leading-relaxed line-clamp-2">
{project.desc} {project.desc}
</p> </p>
<button <button className="group/btn flex items-center gap-2 text-white font-bold text-sm tracking-widest uppercase">
onClick={() => { Voir le projet
setSelectedProject(project);
setCurrentImageIndex(0);
}}
className="group/btn flex items-center gap-2 text-white font-bold text-sm tracking-widest uppercase"
>
Voir plus
<span className="material-symbols-outlined text-sm group-hover/btn:translate-x-1 transition-transform"> <span className="material-symbols-outlined text-sm group-hover/btn:translate-x-1 transition-transform">
arrow_forward arrow_forward
</span> </span>
@ -364,52 +361,41 @@ export default function Home() {
))} ))}
</div> </div>
{(() => { {/* Professional Reveal Zone */}
const extraProjects = allProjects.slice(4, 12); // Adjusted slice for extra projects <div className="relative mt-12 md:mt-16">
return (
<div className="relative mt-32 flex flex-col items-center">
{/* Liquid Reveal Projects Container */}
<div <div
className={`relative w-full transition-all duration-[2000ms] ease-[cubic-bezier(0.85,0,0.15,1)] origin-top overflow-hidden className={`relative w-full transition-all duration-1000 ease-[cubic-bezier(0.23,1,0.32,1)] overflow-hidden
${showAllProjects ? "max-h-[5000px] opacity-100 pb-64" : "max-h-0 opacity-0"}`} ${showAllProjects ? "max-h-[2000px] opacity-100" : "max-h-0 opacity-0"}`}
> >
{/* Branded Expansion Background */} <div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-16 pb-32">
<div className="absolute inset-0 -mx-[50vw] left-1/2 w-[200vw] h-full bg-zinc-50 dark:bg-white/5 pointer-events-none" />
<div className="absolute inset-0 -mx-[50vw] left-1/2 w-[200vw] h-full pointer-events-none opacity-50"
style={{ background: 'radial-gradient(circle at 50% 0%, var(--primary), transparent 70%)' }} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 relative z-10 container mx-auto px-6 pt-32">
{extraProjects.map((project, idx) => ( {extraProjects.map((project, idx) => (
<div <div
key={idx + 4} key={idx + 4}
className={`group relative overflow-hidden rounded-[3rem] aspect-[4/3] cursor-pointer shadow-2xl ${project.offset ? "md:translate-y-20" : "" className="group relative overflow-hidden rounded-[3rem] aspect-[4/3] cursor-pointer shadow-2xl"
}`} onClick={() => {
setSelectedProject(project);
setCurrentImageIndex(0);
}}
> >
<img <img
src={project.img} src={project.img}
alt={project.title} alt={project.title}
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110" className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110"
/> />
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 flex flex-col justify-end p-12"> <div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 flex flex-col justify-end p-10 md:p-12">
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-primary font-bold text-xs uppercase tracking-widest mb-1"> <span className="text-primary font-bold text-xs uppercase tracking-widest mb-1">
{project.categories.join(" / ")} {project.categories.join(" / ")}
</span> </span>
<h3 className="text-white text-2xl font-black mb-4"> <h3 className="text-white text-2xl md:text-3xl font-black mb-4">
{project.title} {project.title}
</h3> </h3>
</div> </div>
<p className="text-white/60 mb-8 text-sm leading-relaxed line-clamp-2"> <p className="text-white/60 mb-8 text-sm leading-relaxed line-clamp-2">
{project.desc} {project.desc}
</p> </p>
<button <button className="group/btn flex items-center gap-2 text-white font-bold text-sm tracking-widest uppercase">
onClick={() => { Voir le projet
setSelectedProject(project);
setCurrentImageIndex(0);
}}
className="group/btn flex items-center gap-2 text-white font-bold text-sm tracking-widest uppercase"
>
Voir plus
<span className="material-symbols-outlined text-sm group-hover/btn:translate-x-1 transition-transform"> <span className="material-symbols-outlined text-sm group-hover/btn:translate-x-1 transition-transform">
arrow_forward arrow_forward
</span> </span>
@ -420,39 +406,24 @@ export default function Home() {
</div> </div>
</div> </div>
{/* Surface & Button (Shown when not expanded) */}
{!showAllProjects && ( {!showAllProjects && (
<div className="w-full flex flex-col items-center"> <div className="flex flex-col items-center pt-8">
{/* The Liquid Base - High Visibility Curve */} <div className="w-24 h-px bg-slate-200 dark:bg-white/10 mb-12" />
<div className="w-screen -mx-[50vw] left-1/2 relative h-80 overflow-visible pointer-events-none -mb-40">
{/* Deep Curved Glow */}
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-screen h-[400px] bg-primary/20 blur-[120px] rounded-full opacity-60" />
<svg viewBox="0 0 1440 200" preserveAspectRatio="none" className="w-full h-40 absolute top-0 fill-primary/10 stroke-primary/20 stroke-2">
<path d="M0,200 Q720,0 1440,200 Z" />
</svg>
</div>
<button <button
onClick={() => setShowAllProjects(true)} onClick={() => setShowAllProjects(true)}
className="group relative inline-flex items-center gap-8 px-14 py-7 bg-[#0A0F1C] text-white rounded-[4rem] font-black text-lg tracking-[0.2em] uppercase transition-all duration-500 hover:scale-105 hover:shadow-[0_20px_60px_-15px_rgba(30,174,188,0.5)] z-20" className="group relative inline-flex items-center gap-6 px-12 py-6 bg-slate-900 dark:bg-white text-white dark:text-slate-900 rounded-2xl font-black text-base tracking-widest uppercase transition-all duration-300 hover:scale-105 active:scale-95 shadow-xl"
> >
<div className="flex flex-col items-start text-left"> <span>Découvrir plus de projets</span>
<span className="text-[10px] text-primary mb-1 tracking-[0.4em]">Découverte</span> <span className="material-symbols-outlined transition-transform duration-300 group-hover:translate-y-1">expand_more</span>
<span className="relative z-10">Découvrir plus de projets</span>
</div>
<div className="relative z-10 w-14 h-14 rounded-2xl bg-gradient-to-br from-primary to-primary-hover flex items-center justify-center group-hover:translate-y-2 transition-all duration-500 shadow-lg shadow-primary/20">
<span className="material-symbols-outlined text-white text-3xl font-bold">expand_more</span>
</div>
<div className="absolute inset-x-0 -bottom-1 h-2 bg-gradient-to-r from-transparent via-primary to-transparent opacity-0 group-hover:opacity-100 transition-opacity blur-md" />
</button> </button>
</div> </div>
)} )}
</div> </div>
);
})()}
</div> </div>
); );
})()} })()}
</div>
</section>
{/* Contact Form Section - REVISED LAYOUT to match Image */} {/* Contact Form Section - REVISED LAYOUT to match Image */}
<section className="bg-white dark:bg-background-dark py-32" id="contact"> <section className="bg-white dark:bg-background-dark py-32" id="contact">
<div className="container mx-auto px-6"> <div className="container mx-auto px-6">
@ -607,9 +578,9 @@ export default function Home() {
<div className="relative bg-white dark:bg-slate-900 w-full max-w-4xl max-h-[90vh] overflow-y-auto rounded-3xl shadow-2xl flex flex-col md:flex-row overflow-hidden border border-slate-200 dark:border-slate-800 animate-in fade-in zoom-in duration-300"> <div className="relative bg-white dark:bg-slate-900 w-full max-w-4xl max-h-[90vh] overflow-y-auto rounded-3xl shadow-2xl flex flex-col md:flex-row overflow-hidden border border-slate-200 dark:border-slate-800 animate-in fade-in zoom-in duration-300">
<button <button
onClick={() => setSelectedProject(null)} onClick={() => setSelectedProject(null)}
className="absolute top-6 right-6 z-20 bg-white/10 backdrop-blur-md text-slate-900 dark:text-white w-10 h-10 rounded-full flex items-center justify-center hover:bg-white/20 transition-all" className="absolute top-6 right-6 z-20 bg-white/10 backdrop-blur-md text-slate-900 dark:text-white w-10 h-10 rounded-full flex items-center justify-center hover:bg-white/20 hover:rotate-90 hover:scale-110 transition-all duration-300 group/close"
> >
<span className="material-symbols-outlined text-xl">close</span> <span className="material-symbols-outlined text-xl transition-transform">close</span>
</button> </button>
<div className="md:w-1/2 relative h-[400px] md:h-auto overflow-hidden group/modal"> <div className="md:w-1/2 relative h-[400px] md:h-auto overflow-hidden group/modal">
@ -676,7 +647,7 @@ export default function Home() {
<div className="flex justify-end flex-grow"> <div className="flex justify-end flex-grow">
<button <button
onClick={() => setSelectedProject(null)} onClick={() => setSelectedProject(null)}
className="bg-primary hover:bg-primary-hover text-white px-10 py-4 rounded-xl font-bold transition-all w-fit shadow-xl" className="bg-primary hover:bg-primary-hover text-white px-10 py-4 rounded-xl font-bold transition-all duration-300 w-fit shadow-xl hover:scale-105 hover:shadow-primary/30 active:scale-95"
> >
Fermer Fermer
</button> </button>
@ -685,8 +656,7 @@ export default function Home() {
</div> </div>
</div> </div>
</div> </div>
) )}
}
</div> </div>
); );
} }