feat: Transform HookLab to OBC Maçonnerie showcase site
Complete transformation of the Next.js project into a professional showcase site for OBC Maçonnerie (Benoît Colin, maçon in Nord 59). Key changes: - Remove all HookLab/Sanity/Supabase/Stripe/admin/training infrastructure - Full OBC Maçonnerie identity: logo, colors, contact info, SIREN - Schema.org LocalBusiness structured data for Benoît Colin - SEO metadata for all pages targeting Nord 59 keywords New pages created (23 total): - Home page with 10 sections (hero, services, pillars, partners, zone, realisations, testimonials, FAQ, contact form, footer) - Service pages: construction-maison, renovation, assainissement, creation-acces, demolition, services - Secondary pages: realisations, partenaires, contact - Blog: listing + 6 SEO articles with static content - 8 local SEO pages: Orchies, Douai, Valenciennes, Mouchin, Flines-lès-Raches, Saint-Amand-les-Eaux - Legal pages: mentions-legales, cgv, confidentialite (OBC adapted) Components: - Navbar with OBC branding + mobile menu - Footer with dark navy theme, services + navigation links - ContactForm client component (devis request) - LocalSEOPage reusable component for local SEO pages - CookieBanner updated with OBC cookie key Config: - layout.tsx: OBC metadata, Schema.org, no Sanity CDN - globals.css: stone color variables added - next.config.ts: removed Sanity CDN remotePatterns - sitemap.ts: all 30 OBC pages - robots.ts: allow all except /api/ - api/contact/route.ts: OBC devis email template https://claude.ai/code/session_01Uec4iHjcPwB1pU41idWEdF
This commit is contained in:
@@ -1,102 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import ScrollReveal from "@/components/animations/ScrollReveal";
|
||||
|
||||
interface AboutMeProps {
|
||||
images?: Record<string, string>;
|
||||
}
|
||||
|
||||
export default function AboutMe({ images }: AboutMeProps) {
|
||||
const photoUrl = images?.about_photo;
|
||||
|
||||
return (
|
||||
<section id="qui-suis-je" className="py-16 md:py-24 bg-orange relative overflow-hidden" aria-label="Qui suis-je">
|
||||
{/* Subtle pattern */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute top-20 right-20 w-40 h-40 border-2 border-white rounded-full" />
|
||||
<div className="absolute bottom-10 left-10 w-60 h-60 border-2 border-white rounded-full" />
|
||||
</div>
|
||||
|
||||
<div className="relative max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Content */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
|
||||
{/* Photo */}
|
||||
<ScrollReveal direction="left">
|
||||
<div className="flex justify-center">
|
||||
<div className="relative">
|
||||
<div className="w-64 h-80 sm:w-72 sm:h-[22rem] rounded-2xl overflow-hidden border-4 border-white/20 shadow-xl">
|
||||
{photoUrl ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={photoUrl} alt="Enguerrand Ozano" className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<div className="w-full h-full bg-orange-hover flex items-center justify-center">
|
||||
<div className="text-center p-6">
|
||||
<div className="w-20 h-20 bg-white/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-10 h-10 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-white/60 text-sm">Votre photo ici</p>
|
||||
<p className="text-white/40 text-xs mt-1">(modifiable dans Admin > Images)</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute -bottom-3 left-1/2 -translate-x-1/2 bg-navy text-white text-xs font-bold px-4 py-2 rounded-full shadow-lg whitespace-nowrap">
|
||||
Basé à Flines-lez-Raches
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
|
||||
{/* Text */}
|
||||
<ScrollReveal direction="right">
|
||||
<div>
|
||||
<span className="inline-block px-3 py-1.5 bg-white/15 rounded-full text-white text-xs font-semibold mb-4">
|
||||
Votre expert local
|
||||
</span>
|
||||
<h2 className="text-2xl md:text-3xl lg:text-4xl font-bold text-white tracking-[-0.02em] mb-4">
|
||||
Enguerrand Ozano.{" "}
|
||||
<span className="text-navy">Votre voisin à Flines-lez-Raches.</span>
|
||||
</h2>
|
||||
<p className="text-white/90 text-base leading-relaxed mb-6">
|
||||
Oubliez les plateformes téléphoniques à l’autre bout du monde.
|
||||
Je suis ici, dans le Nord (59). Je connais la réalité de vos métiers
|
||||
et vos contraintes géographiques.
|
||||
</p>
|
||||
|
||||
<ul className="space-y-4 mb-6">
|
||||
{[
|
||||
{ strong: "Un interlocuteur unique", text: "C\u2019est moi qui g\u00e8re votre dossier du d\u00e9but \u00e0 la fin." },
|
||||
{ strong: "100% G\u00e9r\u00e9 pour vous", text: "Une fois le site lanc\u00e9, vous n\u2019avez rien \u00e0 faire. Si vous avez une nouvelle photo de chantier, vous me l\u2019envoyez, je la mets en ligne." },
|
||||
{ strong: "Pas de mauvaise surprise", text: "Tout est clair d\u00e8s le d\u00e9part." },
|
||||
].map((item, i) => (
|
||||
<li key={i} className="flex items-start gap-3">
|
||||
<div className="w-5 h-5 bg-white/20 rounded-full flex items-center justify-center shrink-0 mt-0.5">
|
||||
<svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-white/80 text-base leading-relaxed">
|
||||
<strong className="text-white">{item.strong} :</strong> {item.text}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<a
|
||||
href="#contact"
|
||||
className="inline-flex items-center gap-2 bg-navy hover:bg-navy-light text-white font-bold text-sm px-6 py-3 rounded-xl transition-colors"
|
||||
>
|
||||
Discutons de votre situation
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function AnnouncementBar() {
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<div className="relative bg-gradient-to-r from-primary via-primary-hover to-primary text-white text-center py-2 px-10 text-xs sm:text-sm font-medium">
|
||||
<Link href="/candidature" className="hover:underline">
|
||||
<span className="hidden sm:inline">
|
||||
Places limitées — Nouvelle session de formation TikTok Shop ouverte →{" "}
|
||||
<span className="underline font-bold">Candidater</span>
|
||||
</span>
|
||||
<span className="sm:hidden">
|
||||
Places limitées —{" "}
|
||||
<span className="underline font-bold">Candidater maintenant</span>
|
||||
</span>
|
||||
</Link>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setVisible(false);
|
||||
}}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-white/70 hover:text-white cursor-pointer p-1"
|
||||
aria-label="Fermer"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
223
components/marketing/ContactForm.tsx
Normal file
223
components/marketing/ContactForm.tsx
Normal file
@@ -0,0 +1,223 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
const typesProjets = [
|
||||
"Construction de maison",
|
||||
"Rénovation",
|
||||
"Assainissement",
|
||||
"Création d'accès",
|
||||
"Démolition",
|
||||
"Autre",
|
||||
];
|
||||
|
||||
export default function ContactForm() {
|
||||
const [form, setForm] = useState({
|
||||
nom: "",
|
||||
telephone: "",
|
||||
email: "",
|
||||
typeProjet: "",
|
||||
description: "",
|
||||
budget: "",
|
||||
zone: "",
|
||||
});
|
||||
const [status, setStatus] = useState<"idle" | "sending" | "success" | "error">("idle");
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
|
||||
) => {
|
||||
setForm((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!form.nom || !form.telephone || !form.typeProjet) {
|
||||
setError("Merci de renseigner au minimum votre nom, téléphone et type de projet.");
|
||||
return;
|
||||
}
|
||||
setError("");
|
||||
setStatus("sending");
|
||||
try {
|
||||
const res = await fetch("/api/contact", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(form),
|
||||
});
|
||||
if (res.ok) {
|
||||
setStatus("success");
|
||||
setForm({ nom: "", telephone: "", email: "", typeProjet: "", description: "", budget: "", zone: "" });
|
||||
} else {
|
||||
setStatus("error");
|
||||
}
|
||||
} catch {
|
||||
setStatus("error");
|
||||
}
|
||||
};
|
||||
|
||||
if (status === "success") {
|
||||
return (
|
||||
<div className="bg-bg-white border border-success rounded-2xl p-8 text-center">
|
||||
<div className="text-4xl mb-4">✅</div>
|
||||
<h3 className="text-navy font-bold text-xl mb-2">Demande envoyée !</h3>
|
||||
<p className="text-text-light text-sm">
|
||||
Benoît vous rappellera dans les 24h. En cas d'urgence, appelez directement le{" "}
|
||||
<a href="tel:0674453089" className="text-orange font-bold">
|
||||
06 74 45 30 89
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
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>
|
||||
<label htmlFor="nom" className="block text-sm font-semibold text-navy mb-1">
|
||||
Nom <span className="text-orange">*</span>
|
||||
</label>
|
||||
<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>
|
||||
<label htmlFor="telephone" className="block text-sm font-semibold text-navy mb-1">
|
||||
Téléphone <span className="text-orange">*</span>
|
||||
</label>
|
||||
<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>
|
||||
<label htmlFor="email" className="block text-sm font-semibold text-navy mb-1">
|
||||
Email
|
||||
</label>
|
||||
<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>
|
||||
<label htmlFor="typeProjet" className="block text-sm font-semibold text-navy mb-1">
|
||||
Type de projet <span className="text-orange">*</span>
|
||||
</label>
|
||||
<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>
|
||||
{typesProjets.map((t) => (
|
||||
<option key={t} value={t}>
|
||||
{t}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="description" className="block text-sm font-semibold text-navy mb-1">
|
||||
Description du projet
|
||||
</label>
|
||||
<textarea
|
||||
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 className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="budget" className="block text-sm font-semibold text-navy mb-1">
|
||||
Budget approximatif
|
||||
</label>
|
||||
<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>
|
||||
<label htmlFor="zone" className="block text-sm font-semibold text-navy mb-1">
|
||||
Commune / Zone
|
||||
</label>
|
||||
<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>
|
||||
|
||||
{error && (
|
||||
<p className="text-error text-sm bg-red-50 border border-red-200 rounded-xl px-4 py-3">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={status === "sending"}
|
||||
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>
|
||||
|
||||
{status === "error" && (
|
||||
<p className="text-error text-sm text-center">
|
||||
Une erreur est survenue. Appelez directement le{" "}
|
||||
<a href="tel:0674453089" className="font-bold underline">
|
||||
06 74 45 30 89
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<p className="text-text-muted text-xs text-center">
|
||||
Devis gratuit & sans engagement — Réponse sous 24h
|
||||
</p>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Card from "@/components/ui/Card";
|
||||
import Link from "next/link";
|
||||
import ScrollReveal from "@/components/animations/ScrollReveal";
|
||||
|
||||
const demos = [
|
||||
{
|
||||
title: "Le Mod\u00e8le \u00ab\u00a0Gros \u0152uvre\u00a0\u00bb",
|
||||
subtitle: "Ma\u00e7ons, Couvreurs",
|
||||
description: "Id\u00e9al pour montrer la technique. Un site qui met en avant vos photos \u00ab\u00a0Avant / Apr\u00e8s\u00a0\u00bb pour prouver la qualit\u00e9 de vos finitions.",
|
||||
cta: "Voir un exemple Ma\u00e7onnerie",
|
||||
href: "/macon",
|
||||
icon: (
|
||||
<svg className="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Le Mod\u00e8le \u00ab\u00a0Cr\u00e9ation\u00a0\u00bb",
|
||||
subtitle: "Paysagistes, Peintres",
|
||||
description: "Id\u00e9al pour vendre du r\u00eave. Un design \u00e9pur\u00e9 qui laisse toute la place \u00e0 la beaut\u00e9 de vos r\u00e9alisations.",
|
||||
cta: "Voir un exemple Paysagiste",
|
||||
href: "/paysagiste",
|
||||
icon: (
|
||||
<svg className="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Le Mod\u00e8le \u00ab\u00a0Intervention\u00a0\u00bb",
|
||||
subtitle: "Plombiers, \u00c9lectriciens",
|
||||
description: "Id\u00e9al pour l\u2019urgence. Un site ultra-rapide avec votre num\u00e9ro de t\u00e9l\u00e9phone bien visible pour \u00eatre appel\u00e9 en un clic.",
|
||||
cta: "Voir un exemple Plombier",
|
||||
href: "/plombier",
|
||||
icon: (
|
||||
<svg className="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
interface DemosLiveProps {
|
||||
images?: Record<string, string>;
|
||||
}
|
||||
|
||||
export default function DemosLive(_props: DemosLiveProps) {
|
||||
return (
|
||||
<section id="exemples" className="py-16 md:py-24 bg-bg" aria-label="Exemples">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<ScrollReveal direction="up">
|
||||
<div className="text-center mb-14">
|
||||
<span className="inline-block px-3 py-1.5 bg-orange/10 border border-orange/20 rounded-full text-orange text-xs font-semibold mb-4">
|
||||
Exemples
|
||||
</span>
|
||||
<h2 className="text-2xl md:text-3xl lg:text-4xl font-bold text-navy tracking-[-0.02em] mb-3">
|
||||
Ne signez pas les yeux fermés.{" "}
|
||||
<span className="text-orange">Regardez ce que je peux faire pour vous.</span>
|
||||
</h2>
|
||||
<p className="text-text-light text-base md:text-lg max-w-2xl mx-auto">
|
||||
Je ne vous demande pas de me croire sur parole. J’ai préparé des modèles
|
||||
adaptés à votre métier. Cliquez et imaginez votre logo à la place.
|
||||
</p>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{demos.map((demo, i) => (
|
||||
<ScrollReveal key={i} direction="up" delay={i * 200}>
|
||||
<Card hover className="flex flex-col p-0 overflow-hidden h-full">
|
||||
{/* Header visuel */}
|
||||
<div className="bg-navy p-6 text-center">
|
||||
<div className="w-16 h-16 bg-orange/20 rounded-2xl flex items-center justify-center mx-auto mb-3 text-orange">
|
||||
{demo.icon}
|
||||
</div>
|
||||
<h3 className="text-white font-bold text-lg">{demo.title}</h3>
|
||||
<p className="text-orange text-sm font-semibold">{demo.subtitle}</p>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-5 flex-1 flex flex-col">
|
||||
<div className="flex-1">
|
||||
<p className="text-text-light text-sm leading-relaxed">{demo.description}</p>
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<Link
|
||||
href={demo.href}
|
||||
className="mt-5 flex items-center justify-center gap-2 bg-orange text-white font-bold text-sm px-5 py-3 rounded-xl hover:bg-orange/90 hover:scale-[1.02] transition-all duration-300"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
{demo.cta}
|
||||
</Link>
|
||||
</div>
|
||||
</Card>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import Link from "next/link";
|
||||
import Button from "@/components/ui/Button";
|
||||
|
||||
export default function ExitIntentPopup() {
|
||||
const [show, setShow] = useState(false);
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
const lastScrollY = useRef(0);
|
||||
const maxScrollY = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (dismissed) return;
|
||||
|
||||
// Check if already shown this session
|
||||
if (typeof window !== "undefined" && sessionStorage.getItem("hooklab_exit_popup")) {
|
||||
setDismissed(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const triggerPopup = () => {
|
||||
if (!show && !dismissed) {
|
||||
setShow(true);
|
||||
sessionStorage.setItem("hooklab_exit_popup", "1");
|
||||
}
|
||||
};
|
||||
|
||||
// Desktop: mouse leaves viewport at top
|
||||
const handleMouseLeave = (e: MouseEvent) => {
|
||||
if (e.clientY <= 5) {
|
||||
triggerPopup();
|
||||
}
|
||||
};
|
||||
|
||||
// Mobile: user scrolls back up fast after scrolling at least 60% of the page
|
||||
const handleScroll = () => {
|
||||
const currentY = window.scrollY;
|
||||
const pageHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||
const scrollPercent = pageHeight > 0 ? currentY / pageHeight : 0;
|
||||
|
||||
if (currentY > maxScrollY.current) {
|
||||
maxScrollY.current = currentY;
|
||||
}
|
||||
|
||||
// Trigger if user scrolled past 60% of page and then scrolls up by 300px+
|
||||
const scrolledUpAmount = maxScrollY.current - currentY;
|
||||
const maxScrollPercent = pageHeight > 0 ? maxScrollY.current / pageHeight : 0;
|
||||
|
||||
if (maxScrollPercent > 0.6 && scrolledUpAmount > 300 && scrollPercent < 0.4) {
|
||||
triggerPopup();
|
||||
}
|
||||
|
||||
lastScrollY.current = currentY;
|
||||
};
|
||||
|
||||
// Desktop: mouseleave
|
||||
document.addEventListener("mouseleave", handleMouseLeave);
|
||||
|
||||
// Mobile: scroll-based trigger
|
||||
window.addEventListener("scroll", handleScroll, { passive: true });
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mouseleave", handleMouseLeave);
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}, [show, dismissed]);
|
||||
|
||||
const handleClose = () => {
|
||||
setShow(false);
|
||||
setDismissed(true);
|
||||
};
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/70 backdrop-blur-sm"
|
||||
onClick={handleClose}
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div className="relative bg-dark-light border border-dark-border rounded-3xl p-6 sm:p-8 max-w-md w-full shadow-2xl animate-scale-in">
|
||||
{/* Close */}
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="absolute top-4 right-4 text-white/40 hover:text-white cursor-pointer"
|
||||
aria-label="Fermer"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div className="text-center">
|
||||
{/* Icon */}
|
||||
<div className="w-14 h-14 sm:w-16 sm:h-16 gradient-bg rounded-full flex items-center justify-center mx-auto mb-4 sm:mb-5">
|
||||
<svg className="w-7 h-7 sm:w-8 sm:h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl sm:text-2xl font-bold text-white mb-2 sm:mb-3">
|
||||
Tu hésites encore ?
|
||||
</h3>
|
||||
<p className="text-white/60 text-sm mb-5 sm:mb-6 leading-relaxed">
|
||||
TikTok Shop vient d'arriver en France. Le marché n'est pas
|
||||
encore saturé et les premiers créateurs captent
|
||||
l'essentiel des commissions.
|
||||
</p>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-5 sm:mb-6">
|
||||
<div className="bg-dark border border-dark-border rounded-xl p-3">
|
||||
<p className="text-lg sm:text-xl font-bold gradient-text">50,5M€</p>
|
||||
<p className="text-white/40 text-xs">Marché FR en 2 mois</p>
|
||||
</div>
|
||||
<div className="bg-dark border border-dark-border rounded-xl p-3">
|
||||
<p className="text-lg sm:text-xl font-bold gradient-text">10-30%</p>
|
||||
<p className="text-white/40 text-xs">Commission par vente</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link href="/candidature" onClick={handleClose}>
|
||||
<Button size="lg" className="w-full pulse-glow mb-3">
|
||||
Découvrir le programme
|
||||
</Button>
|
||||
</Link>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="text-white/30 text-xs hover:text-white/50 transition-colors cursor-pointer"
|
||||
>
|
||||
Non merci
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,71 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import ScrollReveal from "@/components/animations/ScrollReveal";
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-border py-10 md:py-12 bg-bg-white">
|
||||
<footer className="bg-navy text-white pt-12 pb-6">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<ScrollReveal direction="up">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{/* Brand */}
|
||||
<div>
|
||||
<Link href="/" className="flex items-center gap-2 mb-3" aria-label="HookLab - Accueil">
|
||||
<div className="w-8 h-8 bg-navy rounded-lg flex items-center justify-center">
|
||||
<span className="text-white font-bold text-sm">H</span>
|
||||
</div>
|
||||
<span className="text-lg font-bold text-navy">
|
||||
Hook<span className="text-orange">Lab</span>
|
||||
</span>
|
||||
</Link>
|
||||
<p className="text-text-light text-sm leading-relaxed max-w-xs">
|
||||
Création de sites internet pour artisans.
|
||||
</p>
|
||||
<p className="text-text-muted text-xs mt-3">
|
||||
59148 Flines-lez-Raches
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Expertises SEO */}
|
||||
<div>
|
||||
<h4 className="text-navy font-semibold text-sm mb-4">
|
||||
Expertises
|
||||
</h4>
|
||||
<ul className="space-y-2 text-text-light text-sm">
|
||||
<li>Site internet Couvreur</li>
|
||||
<li>SEO Maçonnerie</li>
|
||||
<li>Webmaster Paysagiste</li>
|
||||
<li>Visibilité Menuisier</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Legal */}
|
||||
<div>
|
||||
<h4 className="text-navy font-semibold text-sm mb-4">Légal</h4>
|
||||
<ul className="space-y-2">
|
||||
<li>
|
||||
<Link href="/mentions-legales" className="text-text-light hover:text-navy text-sm transition-colors">
|
||||
Mentions légales
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/confidentialite" className="text-text-light hover:text-navy text-sm transition-colors">
|
||||
Politique de Confidentialité
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 pb-10 border-b border-white/10">
|
||||
{/* Brand */}
|
||||
<div className="md:col-span-2">
|
||||
<div className="flex items-center gap-2.5 mb-4">
|
||||
<div className="w-10 h-10 bg-orange rounded-lg flex items-center justify-center shrink-0">
|
||||
<span className="text-white font-bold text-xs">OBC</span>
|
||||
</div>
|
||||
<div className="flex flex-col leading-tight">
|
||||
<span className="text-white font-bold text-base leading-none">OBC</span>
|
||||
<span className="text-orange-light font-bold text-base leading-none">Maçonnerie</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-white/70 text-sm leading-relaxed mb-4 max-w-xs">
|
||||
Benoît Colin, maçon expert en construction de maison, rénovation et gros œuvre dans le Nord. De la première pierre à la remise des clés.
|
||||
</p>
|
||||
<a
|
||||
href="tel:0674453089"
|
||||
className="inline-flex items-center gap-2 text-orange-light font-bold text-base hover:text-white transition-colors"
|
||||
>
|
||||
<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>
|
||||
06 74 45 30 89
|
||||
</a>
|
||||
<p className="text-white/40 text-xs mt-2">
|
||||
221 Route de Saint-Amand, 59310 Mouchin
|
||||
</p>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
|
||||
{/* Bottom SEO */}
|
||||
<div className="border-t border-border mt-8 pt-6 flex flex-col md:flex-row items-center justify-between gap-3">
|
||||
<p className="text-text-muted text-xs">
|
||||
© {new Date().getFullYear()} HookLab — Enguerrand Ozano · SIREN 994 538 932
|
||||
{/* Services */}
|
||||
<div>
|
||||
<h4 className="text-white font-semibold text-sm mb-4 uppercase tracking-wide">Services</h4>
|
||||
<ul className="space-y-2">
|
||||
{[
|
||||
{ href: "/construction-maison", label: "Construction de maison" },
|
||||
{ href: "/renovation", label: "Rénovation" },
|
||||
{ href: "/assainissement", label: "Assainissement" },
|
||||
{ href: "/creation-acces", label: "Création d'accès" },
|
||||
{ href: "/demolition", label: "Démolition" },
|
||||
].map((item) => (
|
||||
<li key={item.href}>
|
||||
<Link href={item.href} className="text-white/60 hover:text-white text-sm transition-colors">
|
||||
{item.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<div>
|
||||
<h4 className="text-white font-semibold text-sm mb-4 uppercase tracking-wide">Navigation</h4>
|
||||
<ul className="space-y-2">
|
||||
{[
|
||||
{ href: "/", label: "Accueil" },
|
||||
{ href: "/realisations", label: "Réalisations" },
|
||||
{ href: "/partenaires", label: "Partenaires" },
|
||||
{ href: "/contact", label: "Contact" },
|
||||
{ href: "/blog", label: "Blog" },
|
||||
].map((item) => (
|
||||
<li key={item.href}>
|
||||
<Link href={item.href} className="text-white/60 hover:text-white text-sm transition-colors">
|
||||
{item.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<h4 className="text-white font-semibold text-sm mb-3 mt-5 uppercase tracking-wide">Légal</h4>
|
||||
<ul className="space-y-2">
|
||||
{[
|
||||
{ href: "/mentions-legales", label: "Mentions légales" },
|
||||
{ href: "/confidentialite", label: "Confidentialité" },
|
||||
{ href: "/cgv", label: "CGV" },
|
||||
].map((item) => (
|
||||
<li key={item.href}>
|
||||
<Link href={item.href} className="text-white/60 hover:text-white text-sm transition-colors">
|
||||
{item.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom */}
|
||||
<div className="pt-6 flex flex-col md:flex-row items-center justify-between gap-3">
|
||||
<p className="text-white/40 text-xs text-center md:text-left">
|
||||
© {new Date().getFullYear()} OBC Maçonnerie — Benoît Colin · SIREN 531 827 871
|
||||
</p>
|
||||
<p className="text-text-muted text-xs text-center md:text-right">
|
||||
Intervention : Douai · Orchies · Arleux · Valenciennes
|
||||
<p className="text-white/40 text-xs text-center md:text-right">
|
||||
Orchies · Mouchin · Douai · Valenciennes · Saint-Amand-les-Eaux —{" "}
|
||||
<span className="text-white/30">Site réalisé par HookLab</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
148
components/marketing/LocalSEOPage.tsx
Normal file
148
components/marketing/LocalSEOPage.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import Link from "next/link";
|
||||
import Navbar from "@/components/marketing/Navbar";
|
||||
import Footer from "@/components/marketing/Footer";
|
||||
import ScrollReveal from "@/components/animations/ScrollReveal";
|
||||
import ContactForm from "@/components/marketing/ContactForm";
|
||||
|
||||
interface LocalSEOPageProps {
|
||||
ville: string;
|
||||
departement?: string;
|
||||
servicesPrincipaux: string[];
|
||||
description: string;
|
||||
texteIntro: string;
|
||||
texteLocal: string;
|
||||
distanceMouchin?: string;
|
||||
}
|
||||
|
||||
const services = [
|
||||
{ icon: "🏠", label: "Construction de maison", href: "/construction-maison" },
|
||||
{ icon: "🔨", label: "Rénovation", href: "/renovation" },
|
||||
{ icon: "💧", label: "Assainissement", href: "/assainissement" },
|
||||
{ icon: "🚧", label: "Création d'accès", href: "/creation-acces" },
|
||||
{ icon: "🏗️", label: "Démolition", href: "/demolition" },
|
||||
];
|
||||
|
||||
export default function LocalSEOPage({
|
||||
ville,
|
||||
departement = "Nord (59)",
|
||||
servicesPrincipaux,
|
||||
description,
|
||||
texteIntro,
|
||||
texteLocal,
|
||||
distanceMouchin,
|
||||
}: LocalSEOPageProps) {
|
||||
return (
|
||||
<main id="main-content" className="min-h-screen">
|
||||
<Navbar />
|
||||
|
||||
{/* Hero */}
|
||||
<section className="bg-navy py-16 md:py-24">
|
||||
<div className="max-w-5xl mx-auto px-4 sm:px-6">
|
||||
<div className="max-w-2xl">
|
||||
<ScrollReveal direction="up">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<span className="text-orange">📍</span>
|
||||
<span className="text-white/60 text-sm">{ville} — {departement}</span>
|
||||
</div>
|
||||
<h1 className="text-3xl md:text-5xl font-bold text-white mb-4 leading-tight">
|
||||
Maçon {ville} — Construction & Rénovation
|
||||
</h1>
|
||||
<p className="text-white/70 text-lg mb-8">{texteIntro}</p>
|
||||
{distanceMouchin && (
|
||||
<p className="text-white/40 text-sm mb-6 italic">
|
||||
{distanceMouchin} de Mouchin (siège OBC Maçonnerie)
|
||||
</p>
|
||||
)}
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Link href="/contact" className="inline-flex items-center justify-center gap-2 bg-orange hover:bg-orange-hover text-white font-bold px-7 py-3.5 rounded-xl transition-colors pulse-glow">
|
||||
Demander un devis gratuit
|
||||
</Link>
|
||||
<a href="tel:0674453089" className="inline-flex items-center justify-center gap-2 bg-white/10 hover:bg-white/20 text-white font-semibold px-7 py-3.5 rounded-xl transition-colors border border-white/20">
|
||||
06 74 45 30 89
|
||||
</a>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Services */}
|
||||
<section className="py-14 bg-bg">
|
||||
<div className="max-w-5xl mx-auto px-4 sm:px-6">
|
||||
<ScrollReveal direction="up">
|
||||
<h2 className="text-2xl font-bold text-navy mb-6 text-center">
|
||||
Nos services à {ville}
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
|
||||
{services.map((s, i) => (
|
||||
<ScrollReveal key={s.label} direction="up" delay={i * 60}>
|
||||
<Link
|
||||
href={s.href}
|
||||
className={`group block bg-bg-white border rounded-xl p-4 text-center transition-all hover:shadow-md ${
|
||||
servicesPrincipaux.includes(s.label)
|
||||
? "border-orange"
|
||||
: "border-border hover:border-orange"
|
||||
}`}
|
||||
>
|
||||
<div className="text-2xl mb-2">{s.icon}</div>
|
||||
<p className="text-navy font-semibold text-xs group-hover:text-orange transition-colors leading-snug">
|
||||
{s.label}
|
||||
</p>
|
||||
</Link>
|
||||
</ScrollReveal>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Texte SEO local */}
|
||||
<section className="py-14 bg-stone-bg">
|
||||
<div className="max-w-3xl mx-auto px-4 sm:px-6">
|
||||
<ScrollReveal direction="up">
|
||||
<h2 className="text-2xl font-bold text-navy mb-5">
|
||||
OBC Maçonnerie intervient à {ville}
|
||||
</h2>
|
||||
<div className="text-text-light text-sm leading-relaxed space-y-4">
|
||||
{texteLocal.split("\n").map((para, i) => (
|
||||
<p key={i}>{para}</p>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 bg-bg-white border border-border rounded-xl p-5">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-navy rounded-lg flex items-center justify-center shrink-0">
|
||||
<span className="text-white font-bold text-xs">OBC</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-navy font-bold text-sm">Benoît Colin — OBC Maçonnerie</p>
|
||||
<p className="text-text-muted text-xs">221 Route de Saint-Amand, 59310 Mouchin</p>
|
||||
<a href="tel:0674453089" className="text-orange font-bold text-sm hover:underline">
|
||||
06 74 45 30 89
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Formulaire */}
|
||||
<section className="py-14 bg-bg">
|
||||
<div className="max-w-xl mx-auto px-4 sm:px-6">
|
||||
<ScrollReveal direction="up">
|
||||
<h2 className="text-2xl font-bold text-navy mb-2 text-center">
|
||||
Votre projet à {ville}
|
||||
</h2>
|
||||
<p className="text-text-light text-sm text-center mb-8">Devis gratuit — Réponse sous 24h</p>
|
||||
</ScrollReveal>
|
||||
<ScrollReveal direction="up" delay={100}>
|
||||
<ContactForm />
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -3,46 +3,58 @@
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
const navLinks = [
|
||||
{ href: "/services", label: "Nos services" },
|
||||
{ href: "/realisations", label: "Réalisations" },
|
||||
{ href: "/partenaires", label: "Partenaires" },
|
||||
{ href: "/contact", label: "Contact" },
|
||||
];
|
||||
|
||||
export default function Navbar() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<nav className="sticky top-0 z-50 bg-bg-white/90 backdrop-blur-md border-b border-border" role="navigation" aria-label="Navigation principale">
|
||||
<nav
|
||||
className="sticky top-0 z-50 bg-bg-white/95 backdrop-blur-md border-b border-border"
|
||||
role="navigation"
|
||||
aria-label="Navigation principale"
|
||||
>
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
{/* Logo */}
|
||||
<Link href="/" className="flex items-center gap-2" aria-label="HookLab - Accueil">
|
||||
<div className="w-9 h-9 bg-navy rounded-lg flex items-center justify-center">
|
||||
<span className="text-white font-bold text-base">H</span>
|
||||
<Link href="/" className="flex items-center gap-2.5" aria-label="OBC Maçonnerie - Accueil">
|
||||
<div className="w-9 h-9 bg-navy rounded-lg flex items-center justify-center shrink-0">
|
||||
<span className="text-white font-bold text-sm">OBC</span>
|
||||
</div>
|
||||
<div className="flex flex-col leading-tight">
|
||||
<span className="text-navy font-bold text-sm leading-none">OBC</span>
|
||||
<span className="text-orange font-bold text-sm leading-none">Maçonnerie</span>
|
||||
</div>
|
||||
<span className="text-xl font-bold text-navy">
|
||||
Hook<span className="text-orange">Lab</span>
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* Desktop links */}
|
||||
<div className="hidden md:flex items-center gap-8">
|
||||
<a href="#methode" className="text-text-light hover:text-navy text-sm font-medium transition-colors">
|
||||
Notre Méthode
|
||||
</a>
|
||||
<a href="#exemples" className="text-text-light hover:text-navy text-sm font-medium transition-colors">
|
||||
Exemples
|
||||
</a>
|
||||
<a href="#qui-suis-je" className="text-text-light hover:text-navy text-sm font-medium transition-colors">
|
||||
Qui suis-je
|
||||
</a>
|
||||
<div className="hidden md:flex items-center gap-6">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="text-text-light hover:text-navy text-sm font-medium transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* CTA desktop - Phone */}
|
||||
{/* CTA desktop */}
|
||||
<div className="hidden md:block">
|
||||
<a
|
||||
href="tel:+33604408157"
|
||||
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/90 transition-colors"
|
||||
href="tel:0674453089"
|
||||
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 pulse-glow"
|
||||
>
|
||||
<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>
|
||||
06 04 40 81 57
|
||||
06 74 45 30 89
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -67,26 +79,29 @@ export default function Navbar() {
|
||||
|
||||
{/* Mobile menu */}
|
||||
{open && (
|
||||
<div className="md:hidden border-t border-border py-4 space-y-3">
|
||||
<a href="#methode" onClick={() => setOpen(false)} className="block text-text-light hover:text-navy text-sm font-medium py-2 transition-colors">
|
||||
Notre Méthode
|
||||
</a>
|
||||
<a href="#exemples" onClick={() => setOpen(false)} className="block text-text-light hover:text-navy text-sm font-medium py-2 transition-colors">
|
||||
Exemples
|
||||
</a>
|
||||
<a href="#qui-suis-je" onClick={() => setOpen(false)} className="block text-text-light hover:text-navy text-sm font-medium py-2 transition-colors">
|
||||
Qui suis-je
|
||||
</a>
|
||||
<a
|
||||
href="tel:+33604408157"
|
||||
onClick={() => setOpen(false)}
|
||||
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">
|
||||
<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>
|
||||
06 04 40 81 57
|
||||
</a>
|
||||
<div className="md:hidden border-t border-border py-4 space-y-1">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
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"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
<div className="pt-2">
|
||||
<a
|
||||
href="tel:0674453089"
|
||||
onClick={() => setOpen(false)}
|
||||
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">
|
||||
<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>
|
||||
Appeler Benoît — 06 74 45 30 89
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
import Card from "@/components/ui/Card";
|
||||
|
||||
const personas = [
|
||||
{
|
||||
id: "jeune",
|
||||
emoji: "🎓",
|
||||
title: "Étudiant / Jeune actif",
|
||||
subtitle: "18-25 ans",
|
||||
description:
|
||||
"Tu veux générer tes premiers revenus en ligne tout en étudiant ou en début de carrière. TikTok Shop est le levier parfait.",
|
||||
benefits: [
|
||||
"Flexibilité totale, travaille quand tu veux",
|
||||
"Pas besoin de stock ni d'investissement",
|
||||
"Compétences marketing valorisables sur ton CV",
|
||||
"Communauté de jeunes entrepreneurs motivés",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "parent",
|
||||
emoji: "👨👩👧",
|
||||
title: "Parent / Reconversion",
|
||||
subtitle: "25-45 ans",
|
||||
description:
|
||||
"Tu cherches un complément de revenus ou une reconversion flexible depuis chez toi. TikTok Shop s'adapte à ton emploi du temps.",
|
||||
benefits: [
|
||||
"2h par jour suffisent pour démarrer",
|
||||
"Travaille depuis chez toi, à ton rythme",
|
||||
"Revenus complémentaires dès le premier mois",
|
||||
"Accompagnement personnalisé et bienveillant",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function PersonaCards() {
|
||||
return (
|
||||
<section className="py-20 md:py-32 bg-dark-light/30">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Header */}
|
||||
<div className="text-center max-w-2xl mx-auto mb-16">
|
||||
<span className="inline-block px-3 py-1.5 bg-primary/10 border border-primary/20 rounded-full text-primary text-xs font-medium mb-4">
|
||||
Pour qui ?
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold tracking-[-0.02em] mb-4">
|
||||
Un programme adapté à{" "}
|
||||
<span className="gradient-text">ton profil</span>
|
||||
</h2>
|
||||
<p className="text-white/60 text-lg">
|
||||
Que tu sois étudiant ou parent, notre méthode s'adapte à toi.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-4xl mx-auto">
|
||||
{personas.map((p) => (
|
||||
<Card key={p.id} hover className="relative overflow-hidden">
|
||||
{/* Gradient accent top */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 gradient-bg" />
|
||||
|
||||
<div className="pt-2">
|
||||
{/* Emoji + Title */}
|
||||
<div className="text-4xl mb-4">{p.emoji}</div>
|
||||
<h3 className="text-xl font-bold text-white mb-1">
|
||||
{p.title}
|
||||
</h3>
|
||||
<p className="text-primary text-sm font-medium mb-3">
|
||||
{p.subtitle}
|
||||
</p>
|
||||
<p className="text-white/60 text-sm mb-6 leading-relaxed">
|
||||
{p.description}
|
||||
</p>
|
||||
|
||||
{/* Benefits */}
|
||||
<ul className="space-y-3">
|
||||
{p.benefits.map((b, i) => (
|
||||
<li key={i} className="flex items-start gap-3">
|
||||
<svg
|
||||
className="w-5 h-5 text-success mt-0.5 shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-white/70 text-sm">{b}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import Button from "@/components/ui/Button";
|
||||
import Card from "@/components/ui/Card";
|
||||
|
||||
const coachingFeatures = [
|
||||
"8 semaines de coaching intensif",
|
||||
"Acc\u00e8s \u00e0 tous les modules vid\u00e9o",
|
||||
"Templates et scripts de contenu",
|
||||
"Appels de groupe hebdomadaires",
|
||||
"Support WhatsApp illimit\u00e9",
|
||||
"Communaut\u00e9 priv\u00e9e d\u2019entrepreneurs",
|
||||
"Mises \u00e0 jour \u00e0 vie du contenu",
|
||||
"Certification HookLab",
|
||||
];
|
||||
|
||||
const bonuses = [
|
||||
"Liste de 50 produits gagnants TikTok Shop",
|
||||
"Guide de l\u2019algorithme TikTok 2025-2026",
|
||||
"Templates Canva pour miniatures",
|
||||
];
|
||||
|
||||
const suiviFeatures = [
|
||||
"R\u00e9ponses \u00e0 tes questions vid\u00e9os",
|
||||
"Id\u00e9es de concepts et tendances",
|
||||
"Coaching motivation au quotidien",
|
||||
"Acc\u00e8s aux nouvelles mises \u00e0 jour",
|
||||
"Groupe WhatsApp alumni",
|
||||
];
|
||||
|
||||
export default function Pricing() {
|
||||
return (
|
||||
<section id="tarif" className="py-20 md:py-32">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Header */}
|
||||
<div className="text-center max-w-2xl mx-auto mb-16">
|
||||
<span className="inline-block px-3 py-1.5 bg-primary/10 border border-primary/20 rounded-full text-primary text-xs font-medium mb-4">
|
||||
Tarifs
|
||||
</span>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold tracking-[-0.02em] mb-4">
|
||||
Investis dans ta{" "}
|
||||
<span className="gradient-text">formation TikTok Shop</span>
|
||||
</h2>
|
||||
<p className="text-white/60 text-lg">
|
||||
Un programme complet avec accompagnement personnalisé, et une
|
||||
option de suivi mensuel pour continuer à progresser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Pricing cards */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||
{/* Coaching card */}
|
||||
<Card className="relative overflow-hidden border-primary/30">
|
||||
{/* Popular badge */}
|
||||
<div className="absolute top-0 left-0 right-0 gradient-bg py-2.5 text-center">
|
||||
<span className="text-white text-sm font-semibold">
|
||||
Programme principal — Places limitées
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="pt-14">
|
||||
<h3 className="text-lg font-bold text-white mb-1">
|
||||
Formation + Coaching
|
||||
</h3>
|
||||
<p className="text-white/40 text-sm mb-6">
|
||||
Programme intensif de 8 semaines
|
||||
</p>
|
||||
|
||||
{/* Price */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="flex items-baseline justify-center gap-2 mb-1">
|
||||
<span className="text-white/40 text-2xl line-through">
|
||||
690€
|
||||
</span>
|
||||
<span className="text-5xl md:text-6xl font-bold text-white">
|
||||
490€
|
||||
</span>
|
||||
<span className="text-white/40 text-lg">/mois</span>
|
||||
</div>
|
||||
<p className="text-white/40 mt-2">
|
||||
x2 mois (980€ total) — Paiement sécurisé
|
||||
via Stripe
|
||||
</p>
|
||||
<div className="inline-flex items-center mt-3 px-3 py-1 bg-success/10 border border-success/20 rounded-full">
|
||||
<span className="text-success text-sm font-medium">
|
||||
Économise 400€ avec l'offre de lancement
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="border-t border-dark-border my-6" />
|
||||
|
||||
{/* Features */}
|
||||
<div className="mb-6">
|
||||
<p className="text-white/50 text-xs uppercase tracking-wider mb-4 font-medium">
|
||||
Inclus dans le programme
|
||||
</p>
|
||||
<ul className="space-y-3">
|
||||
{coachingFeatures.map((f, i) => (
|
||||
<li key={i} className="flex items-center gap-3">
|
||||
<div className="w-5 h-5 rounded-full bg-success/10 flex items-center justify-center shrink-0">
|
||||
<svg
|
||||
className="w-3 h-3 text-success"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-white/80 text-sm">{f}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Bonus */}
|
||||
<div className="bg-primary/5 border border-primary/10 rounded-2xl p-4 mb-6">
|
||||
<p className="text-primary text-xs uppercase tracking-wider mb-3 font-medium">
|
||||
Bonus inclus
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{bonuses.map((b, i) => (
|
||||
<li key={i} className="flex items-center gap-2">
|
||||
<svg
|
||||
className="w-4 h-4 text-primary shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-white/70 text-sm">{b}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<Link href="/candidature">
|
||||
<Button size="lg" className="w-full pulse-glow">
|
||||
Candidater pour rejoindre HookLab
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
{/* Guarantee */}
|
||||
<div className="flex items-center justify-center gap-2 mt-5">
|
||||
<svg
|
||||
className="w-5 h-5 text-success"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-white/40 text-sm">
|
||||
Garantie satisfait ou remboursé 14 jours
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-center text-white/25 text-xs mt-3">
|
||||
Candidature soumise à validation. Réponse sous
|
||||
24h.
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Suivi card */}
|
||||
<Card className="relative overflow-hidden border-dark-border">
|
||||
<div className="absolute top-0 left-0 right-0 bg-dark-lighter py-2.5 text-center border-b border-dark-border">
|
||||
<span className="text-white/60 text-sm font-semibold">
|
||||
Après la formation
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="pt-14">
|
||||
<h3 className="text-lg font-bold text-white mb-1">
|
||||
Suivi continu
|
||||
</h3>
|
||||
<p className="text-white/40 text-sm mb-6">
|
||||
Pour ceux qui ont terminé le programme
|
||||
</p>
|
||||
|
||||
{/* Price */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="flex items-baseline justify-center gap-2 mb-1">
|
||||
<span className="text-4xl md:text-5xl font-bold text-white">
|
||||
49€
|
||||
</span>
|
||||
<span className="text-white/40 text-lg">/mois</span>
|
||||
</div>
|
||||
<p className="text-white/40 mt-2">
|
||||
Sans engagement — Annulable à tout moment
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="border-t border-dark-border my-6" />
|
||||
|
||||
{/* Condition */}
|
||||
<div className="bg-warning/5 border border-warning/15 rounded-xl p-3 mb-6">
|
||||
<p className="text-warning text-xs font-medium flex items-center gap-2">
|
||||
<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="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Accessible uniquement après avoir terminé les 8 semaines de coaching
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<div className="mb-6">
|
||||
<p className="text-white/50 text-xs uppercase tracking-wider mb-4 font-medium">
|
||||
Inclus dans le suivi
|
||||
</p>
|
||||
<ul className="space-y-3">
|
||||
{suiviFeatures.map((f, i) => (
|
||||
<li key={i} className="flex items-center gap-3">
|
||||
<div className="w-5 h-5 rounded-full bg-success/10 flex items-center justify-center shrink-0">
|
||||
<svg
|
||||
className="w-3 h-3 text-success"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-white/80 text-sm">{f}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<div className="bg-dark border border-dark-border rounded-2xl p-4 mb-6">
|
||||
<p className="text-white/50 text-sm leading-relaxed">
|
||||
Le suivi mensuel te permet de continuer à progresser
|
||||
après ta formation initiale. Pose tes questions, reçois
|
||||
des idées de contenus et reste motivé avec la
|
||||
communauté d'anciens élèves.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* CTA disabled-style */}
|
||||
<div className="opacity-60">
|
||||
<Button size="lg" variant="secondary" className="w-full" disabled>
|
||||
Disponible après la formation
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-center text-white/25 text-xs mt-3">
|
||||
Le lien de souscription sera envoyé à la fin de
|
||||
tes 8 semaines.
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const notifications = [
|
||||
{ name: "Mehdi L.", action: "a candidat\u00e9", time: "il y a 3 min" },
|
||||
{ name: "Laura B.", action: "a rejoint le programme", time: "il y a 12 min" },
|
||||
{ name: "Yanis K.", action: "a candidat\u00e9", time: "il y a 18 min" },
|
||||
{ name: "Sarah M.", action: "a g\u00e9n\u00e9r\u00e9 sa 1\u00e8re commission", time: "il y a 1h" },
|
||||
{ name: "Thomas D.", action: "a candidat\u00e9", time: "il y a 2h" },
|
||||
{ name: "Amina K.", action: "a atteint 1 000\u20ac de commissions", time: "il y a 3h" },
|
||||
{ name: "Julien R.", action: "a candidat\u00e9", time: "il y a 4h" },
|
||||
{ name: "Fatima N.", action: "a rejoint le programme", time: "il y a 5h" },
|
||||
];
|
||||
|
||||
export default function SocialProofTicker() {
|
||||
const [current, setCurrent] = useState(0);
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const showTimeout = setTimeout(() => setVisible(true), 5000);
|
||||
return () => clearTimeout(showTimeout);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) return;
|
||||
const interval = setInterval(() => {
|
||||
setVisible(false);
|
||||
setTimeout(() => {
|
||||
setCurrent((prev) => (prev + 1) % notifications.length);
|
||||
setVisible(true);
|
||||
}, 500);
|
||||
}, 4000);
|
||||
return () => clearInterval(interval);
|
||||
}, [visible]);
|
||||
|
||||
const n = notifications[current];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed bottom-4 left-4 z-50 transition-all duration-500 ${
|
||||
visible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
|
||||
}`}
|
||||
>
|
||||
<div className="bg-dark-light border border-dark-border rounded-2xl p-4 shadow-2xl max-w-xs">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-full gradient-bg flex items-center justify-center text-sm font-bold text-white shrink-0">
|
||||
{n.name[0]}
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white text-sm font-medium">
|
||||
{n.name} <span className="text-white/60 font-normal">{n.action}</span>
|
||||
</p>
|
||||
<p className="text-white/40 text-xs">{n.time}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user