feat: refonte UI éditoriale — style Messager + animations boutons

Navbar :
- Style éditorial : liens uppercase tracking-wide, underline slide au hover
- Bouton "Devis gratuit" avec animation slide-fill
- Active state par pathname

Page d'accueil :
- Hero split : texte gauche (clamp typography) + panneau diagonal brique droit
- Services : fond navy-light, cards avec bordure top orange, emoji + arrow animée
- Bande CTA : bg-orange full-width, btn-fill-white + btn-outline-light
- Réalisations : grid avec overlay slide-from-bottom au hover (rouge brique)
- Témoignages : dark navy, cards avec border-top
- Zone intervention : split 2 cols, pills uppercase
- Partenaires : grid 8 cols, grayscale → couleur au hover
- FAQ : split 2 cols, accordion avec icône + rotation
- Contact : split dark/light — infos gauche + formulaire droit

Globals.css :
- Classes .btn, .btn-fill, .btn-outline-dark/light, .btn-fill-white, .btn-arrow
- Animation slide-from-left via ::before pseudo-element
- .realisation-overlay (slide-up), .hero-diagonal-panel (clip-path)
- .nav-link (underline grow), .service-card-dark, .faq-icon

ContactForm + Footer : style éditorial squares, uppercase labels

https://claude.ai/code/session_01Uec4iHjcPwB1pU41idWEdF
This commit is contained in:
Claude
2026-02-27 13:36:10 +00:00
parent a133f195c2
commit 5493d6a660
5 changed files with 763 additions and 475 deletions

View File

@@ -358,3 +358,130 @@ html {
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;
} }
/* ================================================
BOUTONS — animations premium slide-fill
================================================ */
.btn {
position: relative;
overflow: hidden;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-weight: 700;
cursor: pointer;
text-decoration: none;
transition: transform 0.2s ease, box-shadow 0.3s ease;
}
.btn:active { transform: scale(0.97) !important; }
.btn > span, .btn > svg { position: relative; z-index: 1; }
/* Primaire rouge brique — slide depuis la gauche */
.btn-fill { background: var(--color-orange); color: #fff; }
.btn-fill::before {
content: ""; position: absolute; inset: 0;
background: var(--color-orange-hover);
transform: translateX(-101%);
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); z-index: 0;
}
.btn-fill:hover::before { transform: translateX(0); }
.btn-fill:hover { transform: translateY(-3px); box-shadow: 0 12px 32px rgba(139, 26, 26, 0.40); }
/* Outline dark — s'inverse en navy */
.btn-outline-dark { background: transparent; color: var(--color-navy); border: 2px solid var(--color-navy); }
.btn-outline-dark::before {
content: ""; position: absolute; inset: 0;
background: var(--color-navy);
transform: translateX(-101%);
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); z-index: 0;
}
.btn-outline-dark:hover::before { transform: translateX(0); }
.btn-outline-dark:hover { color: #fff; transform: translateY(-2px); }
.btn-outline-dark > span, .btn-outline-dark > svg { position: relative; z-index: 1; }
/* Outline light — pour fonds sombres */
.btn-outline-light { background: transparent; color: #fff; border: 2px solid rgba(255,255,255,0.35); }
.btn-outline-light::before {
content: ""; position: absolute; inset: 0;
background: rgba(255,255,255,0.12);
transform: translateX(-101%);
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); z-index: 0;
}
.btn-outline-light:hover::before { transform: translateX(0); }
.btn-outline-light:hover { border-color: rgba(255,255,255,0.65); transform: translateY(-2px); }
.btn-outline-light > span, .btn-outline-light > svg { position: relative; z-index: 1; }
/* Fill blanc — pour sections colorées */
.btn-fill-white { background: #fff; color: var(--color-navy); }
.btn-fill-white::before {
content: ""; position: absolute; inset: 0;
background: var(--color-bg-muted);
transform: translateX(-101%);
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); z-index: 0;
}
.btn-fill-white:hover::before { transform: translateX(0); }
.btn-fill-white:hover { transform: translateY(-3px); box-shadow: 0 10px 28px rgba(0,0,0,0.25); }
.btn-fill-white > span, .btn-fill-white > svg { position: relative; z-index: 1; }
/* Lien-flèche animé */
.btn-arrow { display: inline-flex; align-items: center; gap: 0.5rem; font-weight: 700; transition: color 0.2s ease; }
.btn-arrow .arrow-icon { display: inline-flex; transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1); }
.btn-arrow:hover .arrow-icon { transform: translateX(7px); }
/* ================================================
NAVBAR — underline animé
================================================ */
.nav-link { position: relative; }
.nav-link::after {
content: ""; position: absolute; bottom: -2px; left: 0;
width: 0; height: 2px; background: var(--color-orange);
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.nav-link:hover::after, .nav-link.active::after { width: 100%; }
/* ================================================
RÉALISATION CARD — slide-up overlay
================================================ */
.realisation-overlay {
transform: translateY(100%);
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.realisation-card:hover .realisation-overlay { transform: translateY(0); }
/* ================================================
HERO — panneau diagonal brique
================================================ */
.hero-diagonal-panel {
position: absolute; top: 0; right: 0;
width: 44%; height: 100%;
background: var(--color-orange);
clip-path: polygon(14% 0%, 100% 0%, 100% 100%, 0% 100%);
opacity: 0.13;
pointer-events: none;
}
.hero-diagonal-border {
position: absolute; top: 0; right: 0;
width: 44%; height: 100%;
clip-path: polygon(14% 0%, 100% 0%, 100% 100%, 0% 100%);
border-left: 3px solid rgba(139,26,26,0.5);
pointer-events: none;
}
/* ================================================
SERVICE CARD — bordure top rouge brique
================================================ */
.service-card-dark {
border-top: 3px solid var(--color-orange);
transition: background 0.3s ease, transform 0.2s ease;
}
.service-card-dark:hover {
background: rgba(255,255,255,0.04);
transform: translateY(-4px);
}
/* ================================================
FAQ — icône rotation
================================================ */
.faq-icon { transition: transform 0.3s ease; }
details[open] .faq-icon { transform: rotate(45deg); }

View File

@@ -24,76 +24,90 @@ export async function generateMetadata(): Promise<Metadata> {
}; };
} }
function ServiceCard({ service }: { service: Service }) { /* ── Arrow SVG ── */
const href = service.slug === "conseil" ? "/contact" : `/${service.slug}`; function Arrow({ className = "w-4 h-4" }: { className?: string }) {
return ( return (
<Link <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
href={href}
className="group block bg-bg-white border border-border rounded-2xl p-6 hover:border-orange hover:shadow-lg transition-all duration-300 card-hover"
>
<div className="text-3xl mb-4">{service.icon}</div>
<h3 className="text-navy font-bold text-lg mb-2 group-hover:text-orange transition-colors">
{service.title}
</h3>
<p className="text-text-light text-sm leading-relaxed">{service.shortDescription}</p>
<div className="mt-4 flex items-center gap-1 text-orange text-sm font-semibold">
En savoir plus
<svg className="w-4 h-4 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg> </svg>
);
}
/* ── Service card — dark bg editorial ── */
function ServiceCard({ service, index }: { service: Service; index: number }) {
const href = service.slug === "conseil" ? "/contact" : `/${service.slug}`;
return (
<Link href={href} className="service-card-dark group block bg-white/[0.03] p-7 cursor-pointer">
<div className="text-3xl mb-5 grayscale group-hover:grayscale-0 transition-all duration-300">
{service.icon}
</div> </div>
<h3 className="text-white font-black text-base uppercase tracking-wider mb-3 group-hover:text-orange transition-colors">
{service.title}
</h3>
<p className="text-white/50 text-sm leading-relaxed mb-6">{service.shortDescription}</p>
<span className="btn-arrow text-orange text-xs uppercase tracking-widest">
Découvrir
<span className="arrow-icon">
<Arrow className="w-3.5 h-3.5" />
</span>
</span>
</Link> </Link>
); );
} }
function StarRating({ note }: { note: number }) { /* ── Testimonial card ── */
return (
<div className="flex gap-0.5">
{Array.from({ length: 5 }).map((_, i) => (
<svg key={i} className={`w-4 h-4 ${i < note ? "text-orange" : "text-border"}`} fill="currentColor" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
))}
</div>
);
}
function TestimonialCard({ t }: { t: Testimonial }) { function TestimonialCard({ t }: { t: Testimonial }) {
const serviceLabel: Record<string, string> = { const serviceLabel: Record<string, string> = {
"construction-maison": "Construction de maison", "construction-maison": "Construction",
renovation: "Rénovation", renovation: "Rénovation",
assainissement: "Assainissement", assainissement: "Assainissement",
"creation-acces": "Création d'accès", "creation-acces": "Création d'accès",
demolition: "Démolition", demolition: "Démolition",
}; };
return ( return (
<div className="bg-white/5 border border-white/10 rounded-2xl p-6 h-full flex flex-col"> <div className="border-t border-white/15 pt-6 flex flex-col h-full">
<StarRating note={t.rating} /> <div className="flex gap-0.5 mb-4">
<p className="text-white/80 text-sm leading-relaxed mt-4 flex-1 italic">&ldquo;{t.text}&rdquo;</p> {Array.from({ length: 5 }).map((_, i) => (
<div className="mt-5 pt-4 border-t border-white/10"> <svg key={i} className={`w-3.5 h-3.5 ${i < t.rating ? "text-orange" : "text-white/15"}`} fill="currentColor" viewBox="0 0 20 20">
<p className="text-white font-semibold text-sm">{t.name}</p> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
<p className="text-white/40 text-xs">{t.ville} {serviceLabel[t.service] ?? t.service}</p> </svg>
))}
</div>
<p className="text-white/70 text-sm leading-relaxed flex-1 italic">&ldquo;{t.text}&rdquo;</p>
<div className="mt-5">
<p className="text-white font-bold text-sm">{t.name}</p>
<p className="text-white/35 text-xs uppercase tracking-wider mt-0.5">
{t.ville} {serviceLabel[t.service] ?? t.service}
</p>
</div> </div>
</div> </div>
); );
} }
function FAQAccordion({ item }: { item: FAQItem }) { /* ── FAQ item ── */
function FAQItem({ item }: { item: FAQItem }) {
return ( return (
<details className="group bg-bg-white border border-border rounded-2xl overflow-hidden"> <details className="group border-b border-border last:border-0">
<summary className="flex items-center justify-between px-6 py-4 cursor-pointer font-semibold text-navy hover:text-orange transition-colors list-none"> <summary className="flex items-center justify-between py-5 cursor-pointer list-none select-none">
<span className="text-navy font-bold text-sm pr-6 group-open:text-orange transition-colors">
{item.question} {item.question}
<svg className="w-5 h-5 text-text-muted group-open:rotate-180 transition-transform shrink-0 ml-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> </span>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> <span className="faq-icon shrink-0 w-5 h-5 flex items-center justify-center text-orange">
<svg width="14" height="14" fill="none" stroke="currentColor" strokeWidth={2.5} viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 5v14M5 12h14" />
</svg> </svg>
</span>
</summary> </summary>
<div className="px-6 pb-5 text-text-light text-sm leading-relaxed border-t border-border-light pt-4"> <div className="pb-5 text-text-light text-sm leading-relaxed -mt-1">
{item.answer} {item.answer}
</div> </div>
</details> </details>
); );
} }
/* ══════════════════════════════════════════════════
PAGE PRINCIPALE
══════════════════════════════════════════════════ */
export default async function HomePage() { export default async function HomePage() {
const [config, services, testimonials, faqItems, values, partners, realisations] = const [config, services, testimonials, faqItems, values, partners, realisations] =
await Promise.all([ await Promise.all([
@@ -106,217 +120,228 @@ export default async function HomePage() {
getRealisations(), getRealisations(),
]); ]);
const { hero, zones, zoneDescription, partnersTitle, partnersMessage, phone, phoneRaw } = config; const { hero, zones, zoneDescription, phone, phoneRaw } = config;
return ( return (
<main id="main-content" className="min-h-screen"> <main id="main-content" className="min-h-screen">
<Navbar /> <Navbar />
{/* ── SECTION 1 — HERO ── */} {/* ══ 1 — HERO ══ */}
<section className="relative bg-navy overflow-hidden texture-dark pt-20 pb-24 md:pt-28 md:pb-32"> <section className="relative bg-navy overflow-hidden texture-dark">
{/* Accent diagonal brique */} {/* Panneaux décoratifs diagonaux */}
<div className="hero-accent" /> <div className="hero-diagonal-panel" />
<div className="hero-accent-line" style={{ right: "30%" }} /> <div className="hero-accent-line" style={{ right: "44%" }} />
<div className="relative max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 text-center"> <div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="inline-flex items-center gap-2 bg-white/8 border border-white/15 rounded-full px-4 py-1.5 mb-8"> <div className="grid lg:grid-cols-5 min-h-[92vh] items-center gap-0">
<span className="w-1.5 h-1.5 bg-orange rounded-full" />
<span className="text-white/70 text-xs font-medium tracking-wide uppercase">{hero.badge}</span>
</div>
<h1 className="text-5xl md:text-7xl font-black text-white leading-none tracking-tight mb-6"> {/* Contenu gauche */}
Maçon &amp;<br /> <div className="lg:col-span-3 py-20 md:py-28">
<span className="text-orange">Constructeur</span><br /> <div className="animate-hero-text-1">
<span className="text-white/60 text-3xl md:text-4xl font-bold tracking-normal">dans le Nord (59)</span> <div className="flex items-center gap-3 mb-8">
</h1> <div className="w-8 h-px bg-orange" />
<span className="text-orange text-xs font-bold uppercase tracking-[0.25em]">
<p className="text-white/60 text-base md:text-lg max-w-xl mx-auto mb-10 leading-relaxed">{hero.subtitle}</p> {hero.badge}
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link href="/contact" className="inline-flex items-center justify-center gap-2 bg-orange hover:bg-orange-hover text-white font-bold px-8 py-4 rounded-xl text-base transition-colors pulse-glow">
{hero.cta}
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</Link>
<Link href="/realisations" className="inline-flex items-center justify-center gap-2 bg-transparent hover:bg-white/8 text-white font-semibold px-8 py-4 rounded-xl text-base transition-colors border border-white/25">
{hero.ctaSecondary}
</Link>
</div>
<div className="mt-16 grid grid-cols-3 gap-8 max-w-md mx-auto border-t border-white/10 pt-10">
{hero.stats.map((s) => (
<div key={s.label} className="text-center">
<div className="text-3xl md:text-4xl font-black text-orange animate-stat-glow">{s.val}</div>
<div className="text-white/40 text-xs mt-1 uppercase tracking-wider">{s.label}</div>
</div>
))}
</div>
</div>
</section>
{/* ── SECTION 2 — NOS SERVICES ── */}
<section className="py-20 md:py-24 bg-bg">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal direction="up">
<div className="text-center mb-12">
<span className="text-orange text-sm font-semibold uppercase tracking-widest">Ce que nous faisons</span>
<h2 className="text-3xl md:text-4xl font-bold text-navy mt-2">Nos services de maçonnerie</h2>
<p className="text-text-light mt-3 max-w-xl mx-auto">
De la construction neuve à la rénovation, Benoît Colin et son équipe prennent en charge tous vos travaux de gros œuvre dans le Nord.
</p>
</div>
</ScrollReveal>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{services.map((s, i) => (
<ScrollReveal key={s.slug} direction="up" delay={i * 80}>
<ServiceCard service={s} />
</ScrollReveal>
))}
</div>
</div>
</section>
{/* ── SECTION 3 — POURQUOI CHOISIR OBC ── */}
<section className="py-20 md:py-24 bg-stone-bg">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal direction="up">
<div className="text-center mb-12">
<span className="text-orange text-sm font-semibold uppercase tracking-widest">Notre différence</span>
<h2 className="text-3xl md:text-4xl font-bold text-navy mt-2">Pourquoi choisir OBC Maçonnerie ?</h2>
</div>
</ScrollReveal>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{values.map((v, i) => (
<ScrollReveal key={v.title} direction="up" delay={i * 100}>
<div className="bg-bg-white rounded-2xl p-6 border border-border text-center h-full">
<div className="text-4xl mb-4">{v.icon}</div>
<h3 className="text-navy font-bold text-lg mb-3">{v.title}</h3>
<p className="text-text-light text-sm leading-relaxed">{v.description}</p>
</div>
</ScrollReveal>
))}
</div>
</div>
</section>
{/* ── SECTION 4 — RÉSEAU PARTENAIRES ── */}
<section className="py-20 md:py-24 bg-bg">
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal direction="up">
<div className="text-center mb-12">
<span className="text-orange text-sm font-semibold uppercase tracking-widest">Un réseau solide</span>
<h2 className="text-3xl md:text-4xl font-bold text-navy mt-2">{partnersTitle}</h2>
<p className="text-text-light mt-4 max-w-xl mx-auto">{partnersMessage}</p>
</div>
</ScrollReveal>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
{partners.map((p, i) => (
<ScrollReveal key={p.label} direction="up" delay={i * 60}>
<div className="bg-bg-white border border-border rounded-xl p-4 text-center hover:border-orange hover:shadow-md transition-all">
<div className="text-2xl mb-2">{p.icon}</div>
<span className="text-navy font-semibold text-sm">{p.label}</span>
</div>
</ScrollReveal>
))}
</div>
<ScrollReveal direction="up" delay={200}>
<div className="mt-10 bg-navy rounded-2xl p-6 md:p-8 text-center">
<p className="text-white text-base md:text-lg font-medium">
Un seul interlocuteur pour coordonner l&apos;ensemble de votre projet de la démolition à la remise des clés.
</p>
<Link href="/partenaires" className="inline-flex items-center gap-2 mt-4 text-orange-light hover:text-white font-semibold transition-colors">
Découvrir notre réseau
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</Link>
</div>
</ScrollReveal>
</div>
</section>
{/* ── SECTION 5 — ZONE D'INTERVENTION ── */}
<section className="py-20 md:py-24 bg-stone-bg">
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<ScrollReveal direction="up">
<span className="text-orange text-sm font-semibold uppercase tracking-widest">Secteur d&apos;activité</span>
<h2 className="text-3xl md:text-4xl font-bold text-navy mt-2 mb-4">Nous intervenons dans toute la région</h2>
<p className="text-text-light max-w-xl mx-auto mb-10">
OBC Maçonnerie intervient dans un rayon de {zoneDescription}.
</p>
</ScrollReveal>
<div className="flex flex-wrap justify-center gap-3 mb-8">
{zones.map((v, i) => (
<ScrollReveal key={v} direction="up" delay={i * 50}>
<span className="inline-flex items-center gap-1.5 bg-bg-white border border-border text-navy font-medium text-sm px-4 py-2 rounded-full hover:border-orange hover:shadow-sm transition-all">
<span className="text-orange">📍</span>{v}
</span> </span>
</div>
<h1 className="text-[clamp(3rem,8vw,6.5rem)] font-black text-white leading-[0.9] tracking-tight uppercase mb-8">
Maçon<br />
<span className="text-orange">&amp;</span><br />
Construc<span className="text-white/30">teur</span>
</h1>
</div>
<p className="animate-hero-text-2 text-white/55 text-base md:text-lg max-w-md leading-relaxed mb-10">
{hero.subtitle}
</p>
<div className="animate-hero-text-3 flex flex-col sm:flex-row gap-4">
<Link
href="/contact"
className="btn btn-fill px-8 py-4 text-xs uppercase tracking-[0.2em]"
>
<span>{hero.cta}</span>
<span className="relative z-10"><Arrow className="w-4 h-4" /></span>
</Link>
<a
href={`tel:${phoneRaw}`}
className="btn btn-outline-light px-8 py-4 text-xs uppercase tracking-[0.2em]"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
<span>{phone}</span>
</a>
</div>
{/* Stats */}
<div className="mt-14 pt-10 border-t border-white/10 flex gap-10">
{hero.stats.map((s) => (
<div key={s.label}>
<div className="text-3xl md:text-4xl font-black text-white animate-stat-glow">
{s.val}
</div>
<div className="text-white/35 text-xs uppercase tracking-widest mt-1">{s.label}</div>
</div>
))}
</div>
</div>
{/* Panneau droit — stats éditoriales sur fond diagonal */}
<div className="hidden lg:flex lg:col-span-2 flex-col justify-center items-start pl-16 py-28 relative z-10">
<div className="space-y-8">
{values.slice(0, 3).map((v, i) => (
<div key={v.title} className={`flex items-start gap-4 animate-hero-text-${Math.min(i + 1, 3)}`}>
<div className="w-10 h-10 border border-orange/40 flex items-center justify-center shrink-0 text-lg">
{v.icon}
</div>
<div>
<p className="text-white font-bold text-sm uppercase tracking-wider">{v.title}</p>
<p className="text-white/40 text-xs mt-1 leading-relaxed max-w-[200px]">{v.description}</p>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</section>
{/* ══ 2 — NOS SERVICES (dark editorial) ══ */}
<section className="bg-navy-light py-20 md:py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal direction="up">
<div className="flex items-end justify-between mb-12 gap-6 flex-wrap">
<div>
<span className="text-orange text-xs font-bold uppercase tracking-[0.25em] block mb-3">
Nos savoir-faire
</span>
<h2 className="text-white font-black text-3xl md:text-5xl uppercase leading-tight tracking-tight">
Nos<br />services
</h2>
</div>
<Link
href="/services"
className="btn-arrow text-white/50 hover:text-white text-xs uppercase tracking-widest"
>
Tous les services
<span className="arrow-icon"><Arrow className="w-3.5 h-3.5" /></span>
</Link>
</div>
</ScrollReveal>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-px bg-white/5">
{services.map((s, i) => (
<ScrollReveal key={s.slug} direction="up" delay={i * 60}>
<ServiceCard service={s} index={i} />
</ScrollReveal> </ScrollReveal>
))} ))}
</div> </div>
<ScrollReveal direction="up" delay={100}> </div>
<p className="text-text-light text-sm italic"> </section>
Et dans toutes les communes à {zoneDescription} contactez-nous pour vérifier votre zone.
{/* ══ 3 — BANDE CTA ══ */}
<section className="bg-orange py-16 md:py-20 relative overflow-hidden">
<div className="absolute inset-0 opacity-5" style={{
backgroundImage: "repeating-linear-gradient(45deg, #fff 0px, #fff 1px, transparent 1px, transparent 50%)",
backgroundSize: "20px 20px"
}} />
<div className="relative max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<ScrollReveal direction="up">
<p className="text-white/70 text-xs font-bold uppercase tracking-[0.3em] mb-4">
Devis gratuit Réponse sous 24h
</p> </p>
<Link href="/contact" className="inline-flex items-center gap-2 mt-6 bg-orange hover:bg-orange-hover text-white font-bold px-7 py-3.5 rounded-xl transition-colors"> <h2 className="text-white font-black text-3xl md:text-5xl uppercase leading-tight tracking-tight mb-8">
Demander un devis dans ma commune Votre projet mérite<br />
un artisan de confiance
</h2>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
href="/contact"
className="btn btn-fill-white px-8 py-4 text-xs uppercase tracking-[0.2em]"
>
<span>Obtenir mon devis gratuit</span>
<span><Arrow className="w-4 h-4" /></span>
</Link> </Link>
<a
href={`tel:${phoneRaw}`}
className="btn btn-outline-light px-8 py-4 text-xs uppercase tracking-[0.2em]"
>
<span>Appeler Benoît {phone}</span>
</a>
</div>
</ScrollReveal> </ScrollReveal>
</div> </div>
</section> </section>
{/* ── SECTION 6 — RÉALISATIONS ── */} {/* ══ 4 — RÉALISATIONS ══ */}
<section className="py-20 md:py-24 bg-bg"> <section className="py-20 md:py-24 bg-bg">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal direction="up"> <ScrollReveal direction="up">
<div className="text-center mb-12"> <div className="flex items-end justify-between mb-12 flex-wrap gap-6">
<span className="text-orange text-sm font-semibold uppercase tracking-widest">Nos chantiers</span> <div>
<h2 className="text-3xl md:text-4xl font-bold text-navy mt-2">Aperçu de nos réalisations</h2> <span className="text-orange text-xs font-bold uppercase tracking-[0.25em] block mb-3">
Nos chantiers
</span>
<h2 className="text-navy font-black text-3xl md:text-5xl uppercase leading-tight tracking-tight">
Réalisations
</h2>
</div>
<Link
href="/realisations"
className="btn-arrow text-text-muted hover:text-navy text-xs uppercase tracking-widest"
>
Voir tout
<span className="arrow-icon"><Arrow className="w-3.5 h-3.5" /></span>
</Link>
</div> </div>
</ScrollReveal> </ScrollReveal>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{realisations.map((r, i) => { {realisations.map((r, i) => {
const colors = ["bg-navy", "bg-stone", "bg-orange"]; const bgColors = ["bg-navy", "bg-stone", "bg-navy-light", "bg-stone", "bg-navy", "bg-navy-light"];
return ( return (
<ScrollReveal key={r.title} direction="up" delay={i * 100}> <ScrollReveal key={r.title} direction="up" delay={i * 80}>
<div className="bg-bg-white rounded-2xl overflow-hidden border border-border hover:shadow-lg transition-all group card-hover"> <div className="realisation-card relative overflow-hidden aspect-[4/3] cursor-pointer group">
<div className={`${colors[i % colors.length]} h-44 flex items-center justify-center`}> {/* Fond placeholder */}
<span className="text-white/20 text-8xl font-bold">{i + 1}</span> <div className={`${bgColors[i % bgColors.length]} w-full h-full flex items-center justify-center`}>
<span className="text-white/8 font-black text-9xl select-none">0{i + 1}</span>
</div> </div>
<div className="p-5"> {/* Ligne orange bas (repos) */}
<span className="inline-block bg-bg-muted text-text-light text-xs font-semibold px-2 py-1 rounded-full mb-2">{r.ville}</span> <div className="absolute bottom-0 left-0 w-full h-0.5 bg-orange/40 transition-all duration-500 group-hover:h-full group-hover:bg-orange/0" />
<h3 className="text-navy font-bold text-base mb-1 group-hover:text-orange transition-colors">{r.title}</h3> {/* Infos au repos */}
<p className="text-text-light text-sm">{r.description}</p> <div className="absolute bottom-0 left-0 right-0 p-5 bg-gradient-to-t from-black/70 to-transparent translate-y-0 group-hover:opacity-0 transition-opacity duration-300">
<span className="text-white/50 text-xs uppercase tracking-widest block mb-1">{r.ville}</span>
<h3 className="text-white font-black text-sm uppercase tracking-wide">{r.title}</h3>
</div>
{/* Overlay hover — slide from bottom */}
<div className="realisation-overlay absolute inset-0 bg-orange flex flex-col justify-end p-6">
<span className="text-white/60 text-xs uppercase tracking-[0.2em] mb-2">{r.ville}</span>
<h3 className="text-white font-black text-lg uppercase tracking-tight mb-2">{r.title}</h3>
<p className="text-white/70 text-xs leading-relaxed">{r.description}</p>
</div> </div>
</div> </div>
</ScrollReveal> </ScrollReveal>
); );
})} })}
</div> </div>
<ScrollReveal direction="up" delay={150}>
<div className="text-center mt-8">
<Link href="/realisations" className="inline-flex items-center gap-2 border-2 border-navy text-navy hover:bg-navy hover:text-white font-bold px-7 py-3.5 rounded-xl transition-colors">
Voir toutes nos réalisations
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</Link>
</div>
</ScrollReveal>
</div> </div>
</section> </section>
{/* ── SECTION 7 — TÉMOIGNAGES ── */} {/* ══ 5 — TÉMOIGNAGES (dark) ══ */}
<section className="py-20 md:py-24 bg-navy"> <section className="py-20 md:py-24 bg-navy texture-dark">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal direction="up"> <ScrollReveal direction="up">
<div className="text-center mb-12"> <div className="mb-14">
<span className="text-orange text-sm font-semibold uppercase tracking-widest">Ce qu&apos;ils en disent</span> <span className="text-orange text-xs font-bold uppercase tracking-[0.25em] block mb-3">
<h2 className="text-3xl md:text-4xl font-bold text-white mt-2">Témoignages clients</h2> Ce qu&apos;ils en disent
</span>
<h2 className="text-white font-black text-3xl md:text-5xl uppercase leading-tight tracking-tight">
Clients<br />satisfaits
</h2>
</div> </div>
</ScrollReveal> </ScrollReveal>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12">
{testimonials.map((t, i) => ( {testimonials.map((t, i) => (
<ScrollReveal key={t.name} direction="up" delay={i * 100}> <ScrollReveal key={t.name} direction="up" delay={i * 100}>
<TestimonialCard t={t} /> <TestimonialCard t={t} />
@@ -326,39 +351,199 @@ export default async function HomePage() {
</div> </div>
</section> </section>
{/* ── SECTION 8 — FAQ ── */} {/* ══ 6 — ZONE D'INTERVENTION ══ */}
<section className="py-20 md:py-24 bg-bg"> <section className="py-20 md:py-24 bg-bg">
<div className="max-w-3xl mx-auto px-4 sm:px-6"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal direction="up"> <div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="text-center mb-12"> <ScrollReveal direction="left">
<span className="text-orange text-sm font-semibold uppercase tracking-widest">Questions fréquentes</span> <span className="text-orange text-xs font-bold uppercase tracking-[0.25em] block mb-4">
<h2 className="text-3xl md:text-4xl font-bold text-navy mt-2">FAQ</h2> Secteur d&apos;activité
</span>
<h2 className="text-navy font-black text-3xl md:text-5xl uppercase leading-tight tracking-tight mb-6">
On intervient<br />dans tout le Nord
</h2>
<p className="text-text-light text-sm leading-relaxed mb-8 max-w-md">
OBC Maçonnerie rayonne sur {zoneDescription} autour de Mouchin de Douai à Valenciennes, Orchies et Saint-Amand-les-Eaux.
</p>
<Link
href="/contact"
className="btn btn-outline-dark px-7 py-3.5 text-xs uppercase tracking-widest"
>
<span>Vérifier ma commune</span>
<span><Arrow className="w-4 h-4" /></span>
</Link>
</ScrollReveal>
<ScrollReveal direction="right">
<div className="flex flex-wrap gap-2">
{zones.map((v, i) => (
<span
key={v}
className="inline-flex items-center gap-1.5 bg-bg-white border border-border text-navy font-bold text-xs px-4 py-2.5 uppercase tracking-wider hover:border-orange hover:text-orange transition-all duration-200 cursor-default"
>
<span className="w-1.5 h-1.5 rounded-full bg-orange shrink-0" />
{v}
</span>
))}
<span className="inline-flex items-center gap-1.5 bg-orange/10 border border-orange/30 text-orange font-bold text-xs px-4 py-2.5 uppercase tracking-wider">
+ communes voisines
</span>
</div> </div>
</ScrollReveal> </ScrollReveal>
<div className="space-y-4"> </div>
{faqItems.map((f, i) => ( </div>
<ScrollReveal key={f.question} direction="up" delay={i * 60}> </section>
<FAQAccordion item={f} />
{/* ══ 7 — PARTENAIRES ══ */}
<section className="py-16 bg-stone-bg border-t border-border">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal direction="up">
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-6 mb-10">
<div>
<span className="text-orange text-xs font-bold uppercase tracking-[0.25em] block mb-2">
Un réseau solide
</span>
<h2 className="text-navy font-black text-xl md:text-2xl uppercase tracking-tight">
Nos partenaires
</h2>
</div>
<Link
href="/partenaires"
className="btn-arrow text-text-muted hover:text-navy text-xs uppercase tracking-widest"
>
Découvrir le réseau
<span className="arrow-icon"><Arrow className="w-3.5 h-3.5" /></span>
</Link>
</div>
</ScrollReveal>
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-8 gap-3">
{partners.map((p, i) => (
<ScrollReveal key={p.label} direction="up" delay={i * 40}>
<div className="group bg-bg-white border border-border p-4 text-center hover:border-orange transition-all duration-200 cursor-default">
<div className="text-xl mb-2 grayscale group-hover:grayscale-0 transition-all">{p.icon}</div>
<span className="text-navy font-bold text-xs uppercase tracking-wide group-hover:text-orange transition-colors">
{p.label}
</span>
</div>
</ScrollReveal> </ScrollReveal>
))} ))}
</div> </div>
</div> </div>
</section> </section>
{/* ── SECTION 9 — FORMULAIRE DE CONTACT ── */} {/* ══ 8 — FAQ ══ */}
<section className="py-20 md:py-24 bg-stone-bg" id="contact"> <section className="py-20 md:py-24 bg-bg">
<div className="max-w-2xl mx-auto px-4 sm:px-6"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal direction="up"> <div className="grid lg:grid-cols-2 gap-16">
<div className="text-center mb-10"> <ScrollReveal direction="left">
<span className="text-orange text-sm font-semibold uppercase tracking-widest">Devis gratuit</span> <span className="text-orange text-xs font-bold uppercase tracking-[0.25em] block mb-4">
<h2 className="text-3xl md:text-4xl font-bold text-navy mt-2">Parlez-nous de votre projet</h2> Questions fréquentes
<p className="text-text-light mt-3"> </span>
Réponse sous 24h ou appelez directement Benoît au{" "} <h2 className="text-navy font-black text-3xl md:text-5xl uppercase leading-tight tracking-tight mb-6">
<a href={`tel:${phoneRaw}`} className="text-orange font-bold hover:underline">{phone}</a> On répond<br />à vos questions
</h2>
<p className="text-text-light text-sm leading-relaxed mb-8 max-w-sm">
Pas de réponse à votre question ? Appelez directement Benoît au{" "}
<a href={`tel:${phoneRaw}`} className="text-orange font-bold hover:underline">{phone}</a>.
</p> </p>
<Link
href="/contact"
className="btn btn-fill px-7 py-3.5 text-xs uppercase tracking-widest"
>
<span>Poser ma question</span>
<span><Arrow className="w-4 h-4" /></span>
</Link>
</ScrollReveal>
<ScrollReveal direction="right">
<div>
{faqItems.map((f) => (
<FAQItem key={f.question} item={f} />
))}
</div> </div>
</ScrollReveal> </ScrollReveal>
<ScrollReveal direction="up" delay={100}> </div>
</div>
</section>
{/* ══ 9 — CONTACT SPLIT ══ */}
<section id="contact" className="grid lg:grid-cols-2">
{/* Gauche — dark info */}
<div className="bg-navy texture-dark py-16 md:py-20 px-8 md:px-12 lg:px-16 relative overflow-hidden">
<div className="hero-accent" />
<ScrollReveal direction="left">
<span className="text-orange text-xs font-bold uppercase tracking-[0.25em] block mb-4">
Contactez-nous
</span>
<h2 className="text-white font-black text-3xl md:text-4xl uppercase leading-tight tracking-tight mb-8">
Parlons de<br />votre projet
</h2>
<div className="space-y-6 mb-10">
<a href={`tel:${phoneRaw}`} className="flex items-center gap-4 group">
<div className="w-11 h-11 border border-orange/40 flex items-center justify-center shrink-0 group-hover:border-orange group-hover:bg-orange/10 transition-all">
<svg className="w-5 h-5 text-orange" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
</div>
<div>
<p className="text-white/40 text-xs uppercase tracking-widest mb-0.5">Téléphone</p>
<p className="text-white font-bold text-lg group-hover:text-orange transition-colors">{phone}</p>
</div>
</a>
<div className="flex items-center gap-4">
<div className="w-11 h-11 border border-white/15 flex items-center justify-center shrink-0">
<svg className="w-5 h-5 text-white/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</div>
<div>
<p className="text-white/40 text-xs uppercase tracking-widest mb-0.5">Siège</p>
<p className="text-white/70 text-sm">{config.address}</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="w-11 h-11 border border-white/15 flex items-center justify-center shrink-0">
<svg className="w-5 h-5 text-white/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div>
<p className="text-white/40 text-xs uppercase tracking-widest mb-0.5">Délai de réponse</p>
<p className="text-white/70 text-sm">Sous 24h Du lundi au samedi</p>
</div>
</div>
</div>
{/* Garanties */}
<div className="border-t border-white/10 pt-8 grid grid-cols-2 gap-4">
{[
{ val: "Gratuit", label: "Devis" },
{ val: "24h", label: "Réponse" },
{ val: "10+", label: "Ans d'expérience" },
{ val: "100%", label: "Satisfaction" },
].map((s) => (
<div key={s.label}>
<div className="text-2xl font-black text-orange">{s.val}</div>
<div className="text-white/35 text-xs uppercase tracking-wider mt-0.5">{s.label}</div>
</div>
))}
</div>
</ScrollReveal>
</div>
{/* Droite — formulaire */}
<div className="bg-stone-bg py-16 md:py-20 px-8 md:px-12 lg:px-16">
<ScrollReveal direction="right">
<span className="text-orange text-xs font-bold uppercase tracking-[0.25em] block mb-4">
Devis gratuit
</span>
<h3 className="text-navy font-black text-2xl uppercase tracking-tight mb-8">
Votre demande
</h3>
<ContactForm /> <ContactForm />
</ScrollReveal> </ScrollReveal>
</div> </div>

View File

@@ -57,165 +57,95 @@ export default function ContactForm() {
if (status === "success") { if (status === "success") {
return ( return (
<div className="bg-bg-white border border-success rounded-2xl p-8 text-center"> <div className="border-t-4 border-success bg-bg-white p-8 text-center">
<div className="text-4xl mb-4"></div> <div className="w-12 h-12 bg-success/10 flex items-center justify-center mx-auto mb-4">
<h3 className="text-navy font-bold text-xl mb-2">Demande envoyée !</h3> <svg className="w-6 h-6 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
</svg>
</div>
<h3 className="text-navy font-black text-lg uppercase tracking-wide mb-2">Demande envoyée !</h3>
<p className="text-text-light text-sm"> <p className="text-text-light text-sm">
Benoît vous rappellera dans les 24h. En cas d&apos;urgence, appelez directement le{" "} Benoît vous rappelle sous 24h. En cas d&apos;urgence :{" "}
<a href="tel:0674453089" className="text-orange font-bold"> <a href="tel:0674453089" className="text-orange font-bold hover:underline">
06 74 45 30 89 06 74 45 30 89
</a> </a>
.
</p> </p>
</div> </div>
); );
} }
const ic = "w-full border border-border bg-bg-white px-4 py-3 text-sm text-text focus:outline-none focus:border-orange transition-colors";
const lc = "block text-xs font-bold uppercase tracking-widest text-navy mb-2";
return ( return (
<form <form onSubmit={handleSubmit} className="space-y-5">
onSubmit={handleSubmit}
className="bg-bg-white border border-border rounded-2xl p-6 md:p-8 space-y-4"
>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div> <div>
<label htmlFor="nom" className="block text-sm font-semibold text-navy mb-1"> <label htmlFor="nom" className={lc}>Nom <span className="text-orange">*</span></label>
Nom <span className="text-orange">*</span> <input id="nom" name="nom" type="text" value={form.nom} onChange={handleChange}
</label> placeholder="Votre nom" className={ic} required />
<input
id="nom"
name="nom"
type="text"
value={form.nom}
onChange={handleChange}
placeholder="Votre nom"
className="w-full border border-border rounded-xl px-4 py-3 text-sm text-text focus:outline-none focus:border-orange transition-colors bg-bg"
required
/>
</div> </div>
<div> <div>
<label htmlFor="telephone" className="block text-sm font-semibold text-navy mb-1"> <label htmlFor="telephone" className={lc}>Téléphone <span className="text-orange">*</span></label>
Téléphone <span className="text-orange">*</span> <input id="telephone" name="telephone" type="tel" value={form.telephone} onChange={handleChange}
</label> placeholder="06 XX XX XX XX" className={ic} required />
<input
id="telephone"
name="telephone"
type="tel"
value={form.telephone}
onChange={handleChange}
placeholder="06 XX XX XX XX"
className="w-full border border-border rounded-xl px-4 py-3 text-sm text-text focus:outline-none focus:border-orange transition-colors bg-bg"
required
/>
</div> </div>
</div> </div>
<div> <div>
<label htmlFor="email" className="block text-sm font-semibold text-navy mb-1"> <label htmlFor="email" className={lc}>Email</label>
Email <input id="email" name="email" type="email" value={form.email} onChange={handleChange}
</label> placeholder="votre@email.fr" className={ic} />
<input
id="email"
name="email"
type="email"
value={form.email}
onChange={handleChange}
placeholder="votre@email.fr"
className="w-full border border-border rounded-xl px-4 py-3 text-sm text-text focus:outline-none focus:border-orange transition-colors bg-bg"
/>
</div> </div>
<div> <div>
<label htmlFor="typeProjet" className="block text-sm font-semibold text-navy mb-1"> <label htmlFor="typeProjet" className={lc}>Type de projet <span className="text-orange">*</span></label>
Type de projet <span className="text-orange">*</span> <select id="typeProjet" name="typeProjet" value={form.typeProjet} onChange={handleChange}
</label> className={ic} required>
<select
id="typeProjet"
name="typeProjet"
value={form.typeProjet}
onChange={handleChange}
className="w-full border border-border rounded-xl px-4 py-3 text-sm text-text focus:outline-none focus:border-orange transition-colors bg-bg"
required
>
<option value="">Choisissez un type de projet</option> <option value="">Choisissez un type de projet</option>
{typesProjets.map((t) => ( {typesProjets.map((t) => <option key={t} value={t}>{t}</option>)}
<option key={t} value={t}>
{t}
</option>
))}
</select> </select>
</div> </div>
<div> <div>
<label htmlFor="description" className="block text-sm font-semibold text-navy mb-1"> <label htmlFor="description" className={lc}>Description du projet</label>
Description du projet <textarea id="description" name="description" value={form.description} onChange={handleChange}
</label> rows={4} placeholder="Surface, localisation, contraintes particulières..."
<textarea className={`${ic} resize-none`} />
id="description"
name="description"
value={form.description}
onChange={handleChange}
rows={4}
placeholder="Décrivez votre projet : surface, localisation, contraintes particulières..."
className="w-full border border-border rounded-xl px-4 py-3 text-sm text-text focus:outline-none focus:border-orange transition-colors bg-bg resize-none"
/>
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div> <div>
<label htmlFor="budget" className="block text-sm font-semibold text-navy mb-1"> <label htmlFor="budget" className={lc}>Budget approximatif</label>
Budget approximatif <input id="budget" name="budget" type="text" value={form.budget} onChange={handleChange}
</label> placeholder="ex : 80 000 €" className={ic} />
<input
id="budget"
name="budget"
type="text"
value={form.budget}
onChange={handleChange}
placeholder="ex : 80 000 €"
className="w-full border border-border rounded-xl px-4 py-3 text-sm text-text focus:outline-none focus:border-orange transition-colors bg-bg"
/>
</div> </div>
<div> <div>
<label htmlFor="zone" className="block text-sm font-semibold text-navy mb-1"> <label htmlFor="zone" className={lc}>Commune / Zone</label>
Commune / Zone <input id="zone" name="zone" type="text" value={form.zone} onChange={handleChange}
</label> placeholder="ex : Orchies, Douai..." className={ic} />
<input
id="zone"
name="zone"
type="text"
value={form.zone}
onChange={handleChange}
placeholder="ex : Orchies, Douai..."
className="w-full border border-border rounded-xl px-4 py-3 text-sm text-text focus:outline-none focus:border-orange transition-colors bg-bg"
/>
</div> </div>
</div> </div>
{error && ( {error && (
<p className="text-error text-sm bg-stone-bg border border-error/30 rounded-xl px-4 py-3"> <p className="text-error text-xs bg-stone-bg border border-error/30 px-4 py-3 uppercase tracking-wide">
{error} {error}
</p> </p>
)} )}
<button <button type="submit" disabled={status === "sending"}
type="submit" className="btn btn-fill w-full py-4 text-xs uppercase tracking-[0.2em] disabled:opacity-60 disabled:cursor-not-allowed">
disabled={status === "sending"} <span>{status === "sending" ? "Envoi en cours..." : "Envoyer ma demande"}</span>
className="w-full bg-orange hover:bg-orange-hover text-white font-bold py-4 rounded-xl transition-colors disabled:opacity-60 disabled:cursor-not-allowed text-base"
>
{status === "sending" ? "Envoi en cours..." : "Envoyer ma demande de devis"}
</button> </button>
{status === "error" && ( {status === "error" && (
<p className="text-error text-sm text-center"> <p className="text-error text-xs text-center">
Une erreur est survenue. Appelez directement le{" "} Erreur. Appelez le{" "}
<a href="tel:0674453089" className="font-bold underline"> <a href="tel:0674453089" className="font-bold underline">06 74 45 30 89</a>.
06 74 45 30 89
</a>
.
</p> </p>
)} )}
<p className="text-text-muted text-xs text-center"> <p className="text-text-muted text-xs text-center uppercase tracking-widest">
Devis gratuit &amp; sans engagement Réponse sous 24h Devis gratuit &amp; sans engagement Réponse sous 24h
</p> </p>
</form> </form>

View File

@@ -15,47 +15,50 @@ export default function Footer() {
} = siteConfig; } = siteConfig;
return ( return (
<footer className="bg-navy text-white pt-12 pb-6"> <footer className="bg-navy-dark text-white">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> {/* Top band */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 pb-10 border-b border-white/10"> <div className="border-b border-white/8">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-16">
<div className="grid grid-cols-1 md:grid-cols-12 gap-10 md:gap-8">
{/* Brand */} {/* Brand */}
<div className="md:col-span-2"> <div className="md:col-span-4">
<div className="flex items-center gap-2.5 mb-4"> <div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 bg-orange rounded-lg flex items-center justify-center shrink-0"> <div className="w-11 h-11 bg-orange flex items-center justify-center shrink-0">
<span className="text-white font-bold text-xs">OBC</span> <span className="text-white font-black text-xs tracking-wider">OBC</span>
</div> </div>
<div className="flex flex-col leading-tight"> <div className="flex flex-col leading-none gap-0.5">
<span className="text-white font-bold text-base leading-none">OBC</span> <span className="text-white font-black text-sm tracking-[0.18em] uppercase">OBC</span>
<span className="text-orange-light font-bold text-base leading-none">Maçonnerie</span> <span className="text-orange-light font-bold text-xs tracking-[0.18em] uppercase">Maçonnerie</span>
</div> </div>
</div> </div>
<p className="text-white/70 text-sm leading-relaxed mb-4 max-w-xs"> <p className="text-white/50 text-sm leading-relaxed mb-6 max-w-xs">
{dirigeant}, maçon expert en construction de maison, rénovation et gros {dirigeant}, maçon expert en construction de maison, rénovation et gros œuvre dans le Nord&nbsp;(59).
œuvre dans le Nord. De la première pierre à la remise des clés.
</p> </p>
<a <a
href={`tel:${phoneRaw}`} href={`tel:${phoneRaw}`}
className="inline-flex items-center gap-2 text-orange-light font-bold text-base hover:text-white transition-colors" className="inline-flex items-center gap-2 text-orange font-bold text-base hover:text-white transition-colors group"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg> </svg>
{phone} {phone}
</a> </a>
<p className="text-white/40 text-xs mt-2">{address}</p> <p className="text-white/30 text-xs mt-2">{address}</p>
</div> </div>
{/* Services */} {/* Services */}
<div> <div className="md:col-span-3">
<h4 className="text-white font-semibold text-sm mb-4 uppercase tracking-wide"> <h4 className="text-white font-black text-xs uppercase tracking-[0.2em] mb-5 flex items-center gap-3">
<span className="w-4 h-px bg-orange" />
Services Services
</h4> </h4>
<ul className="space-y-2"> <ul className="space-y-2.5">
{footerServicesNav.map((item) => ( {footerServicesNav.map((item) => (
<li key={item.href}> <li key={item.href}>
<Link <Link
href={item.href} href={item.href}
className="text-white/60 hover:text-white text-sm transition-colors" className="text-white/45 hover:text-white text-sm transition-colors hover:translate-x-1 inline-block duration-200"
> >
{item.label} {item.label}
</Link> </Link>
@@ -64,51 +67,64 @@ export default function Footer() {
</ul> </ul>
</div> </div>
{/* Navigation + Légal */} {/* Navigation */}
<div> <div className="md:col-span-2">
<h4 className="text-white font-semibold text-sm mb-4 uppercase tracking-wide"> <h4 className="text-white font-black text-xs uppercase tracking-[0.2em] mb-5 flex items-center gap-3">
<span className="w-4 h-px bg-orange" />
Navigation Navigation
</h4> </h4>
<ul className="space-y-2"> <ul className="space-y-2.5">
{footerMainNav.map((item) => ( {footerMainNav.map((item) => (
<li key={item.href}> <li key={item.href}>
<Link <Link
href={item.href} href={item.href}
className="text-white/60 hover:text-white text-sm transition-colors" className="text-white/45 hover:text-white text-sm transition-colors hover:translate-x-1 inline-block duration-200"
> >
{item.label} {item.label}
</Link> </Link>
</li> </li>
))} ))}
</ul> </ul>
<h4 className="text-white font-semibold text-sm mb-3 mt-5 uppercase tracking-wide"> </div>
{/* Légal + CTA */}
<div className="md:col-span-3">
<h4 className="text-white font-black text-xs uppercase tracking-[0.2em] mb-5 flex items-center gap-3">
<span className="w-4 h-px bg-orange" />
Légal Légal
</h4> </h4>
<ul className="space-y-2"> <ul className="space-y-2.5 mb-8">
{footerLegalNav.map((item) => ( {footerLegalNav.map((item) => (
<li key={item.href}> <li key={item.href}>
<Link <Link
href={item.href} href={item.href}
className="text-white/60 hover:text-white text-sm transition-colors" className="text-white/45 hover:text-white text-sm transition-colors hover:translate-x-1 inline-block duration-200"
> >
{item.label} {item.label}
</Link> </Link>
</li> </li>
))} ))}
</ul> </ul>
<Link
href="/contact"
className="btn btn-fill text-xs uppercase tracking-widest px-5 py-3 w-full justify-center"
>
<span>Devis gratuit</span>
</Link>
</div>
</div>
</div> </div>
</div> </div>
{/* Bottom */} {/* Bottom bar */}
<div className="pt-6 flex flex-col md:flex-row items-center justify-between gap-3"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-5">
<p className="text-white/40 text-xs text-center md:text-left"> <div className="flex flex-col md:flex-row items-center justify-between gap-3 text-white/25 text-xs">
&copy; {new Date().getFullYear()} {name} &mdash; {dirigeant} &middot; SIREN{" "} <p>
{siren.replace(/(\d{3})(\d{3})(\d{3})/, "$1 $2 $3")} &copy; {new Date().getFullYear()} {name} &mdash; {dirigeant}{" "}
&middot; SIREN {siren.replace(/(\d{3})(\d{3})(\d{3})/, "$1 $2 $3")}
</p> </p>
<p className="text-white/40 text-xs text-center md:text-right"> <p className="text-center">
Orchies &middot; Mouchin &middot; Douai &middot; Valenciennes &middot; Orchies &middot; Mouchin &middot; Douai &middot; Valenciennes &middot; Saint-Amand-les-Eaux
Saint-Amand-les-Eaux &mdash;{" "}
<span className="text-white/30">Site réalisé par HookLab</span>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -2,100 +2,130 @@
import { useState } from "react"; import { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation";
import { siteConfig } from "@/lib/site-config"; import { siteConfig } from "@/lib/site-config";
export default function Navbar() { export default function Navbar() {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const { name, phone, phoneRaw, nav } = siteConfig; const { phone, phoneRaw, nav } = siteConfig;
const pathname = usePathname();
return ( return (
<nav <nav
className="sticky top-0 z-50 bg-bg-white/95 backdrop-blur-md border-b border-border" className="sticky top-0 z-50 bg-bg-white border-b border-border"
role="navigation" role="navigation"
aria-label="Navigation principale" aria-label="Navigation principale"
> >
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16"> <div className="flex items-center justify-between h-16 md:h-[68px]">
{/* Logo */} {/* Logo */}
<Link href="/" className="flex items-center gap-2.5" aria-label={`${name} - Accueil`}> <Link href="/" className="flex items-center gap-3 shrink-0" aria-label="OBC Maçonnerie — Accueil">
<div className="w-9 h-9 bg-navy rounded-lg flex items-center justify-center shrink-0"> <div className="w-10 h-10 bg-navy flex items-center justify-center shrink-0">
<span className="text-white font-bold text-xs">OBC</span> <span className="text-white font-black text-xs tracking-wider">OBC</span>
</div> </div>
<div className="flex flex-col leading-tight"> <div className="flex flex-col leading-none gap-0.5">
<span className="text-navy font-bold text-sm leading-none">OBC</span> <span className="text-navy font-black text-sm tracking-[0.18em] uppercase">OBC</span>
<span className="text-orange font-bold text-sm leading-none">Maçonnerie</span> <span className="text-orange font-bold text-xs tracking-[0.18em] uppercase">Maçonnerie</span>
</div> </div>
</Link> </Link>
{/* Desktop links */} {/* Desktop nav links */}
<div className="hidden md:flex items-center gap-6"> <div className="hidden lg:flex items-center gap-7">
{nav.map((link) => ( {nav.map((link) => {
const active = pathname === link.href || (link.href !== "/" && pathname.startsWith(link.href));
return (
<Link <Link
key={link.href} key={link.href}
href={link.href} href={link.href}
className="text-text-light hover:text-navy text-sm font-medium transition-colors" className={`nav-link text-xs font-bold uppercase tracking-[0.14em] transition-colors pb-0.5 ${
active ? "text-orange active" : "text-text-light hover:text-navy"
}`}
> >
{link.label} {link.label}
</Link> </Link>
))} );
})}
</div> </div>
{/* CTA desktop */} {/* Right — phone + CTA */}
<div className="hidden md:block"> <div className="hidden lg:flex items-center gap-5">
<a <a
href={`tel:${phoneRaw}`} href={`tel:${phoneRaw}`}
className="inline-flex items-center gap-2 bg-orange text-white font-bold text-sm px-5 py-2.5 rounded-xl hover:bg-orange-hover transition-colors" className="flex items-center gap-2 text-navy font-bold text-sm hover:text-orange transition-colors"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-orange shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg> </svg>
{phone} {phone}
</a> </a>
<Link
href="/contact"
className="btn btn-fill text-xs uppercase tracking-widest px-5 py-2.5"
>
<span>Devis gratuit</span>
</Link>
</div> </div>
{/* Mobile menu button */} {/* Mobile burger */}
<button <button
className="md:hidden p-2 text-text-light hover:text-navy transition-colors cursor-pointer" className="lg:hidden p-2 text-navy transition-colors"
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
aria-label={open ? "Fermer le menu" : "Ouvrir le menu"} aria-label={open ? "Fermer le menu" : "Ouvrir le menu"}
aria-expanded={open} aria-expanded={open}
> >
{open ? ( <div className={`w-5 space-y-1.5 transition-all ${open ? "opacity-0 scale-75" : ""}`}>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> {!open ? (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
) : (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
</svg> </svg>
) : (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
)} )}
</div>
</button> </button>
</div> </div>
{/* Mobile menu */} {/* Mobile menu */}
{open && ( {open && (
<div className="md:hidden border-t border-border py-4 space-y-1"> <div className="lg:hidden border-t border-border pb-5">
{nav.map((link) => ( <div className="pt-2">
{nav.map((link) => {
const active = pathname === link.href || (link.href !== "/" && pathname.startsWith(link.href));
return (
<Link <Link
key={link.href} key={link.href}
href={link.href} href={link.href}
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
className="block text-text-light hover:text-navy text-sm font-medium py-2.5 px-2 rounded-lg hover:bg-bg-muted transition-colors" className={`flex items-center justify-between text-xs font-bold uppercase tracking-widest py-3.5 px-1 border-b border-border-light transition-colors ${
active ? "text-orange" : "text-text-light hover:text-navy"
}`}
> >
{link.label} {link.label}
{active && <span className="w-1.5 h-1.5 rounded-full bg-orange" />}
</Link> </Link>
))} );
<div className="pt-2"> })}
</div>
<div className="mt-5 flex flex-col gap-3">
<a <a
href={`tel:${phoneRaw}`} href={`tel:${phoneRaw}`}
onClick={() => setOpen(false)} className="flex items-center gap-2 text-navy font-bold text-sm"
className="flex items-center justify-center gap-2 bg-orange text-white font-bold text-sm px-5 py-3 rounded-xl mt-2"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-orange" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg> </svg>
{phone} {phone}
</a> </a>
<Link
href="/contact"
onClick={() => setOpen(false)}
className="btn btn-fill text-xs uppercase tracking-widest px-5 py-3.5 w-full justify-center"
>
<span>Demander un devis gratuit</span>
</Link>
</div> </div>
</div> </div>
)} )}