feat: rebuild 3 demo pages with interactive features + local SEO pages

- Rebuild /macon with cert badge popups, before/after slider, intelligent
  form with urgency detection
- Rebuild /paysagiste with filterable gallery, seasonal banner, WhatsApp
  floating button, devis form
- Rebuild /plombier with sticky call bar, 3-step diagnostic wizard,
  transparent tariffs, zone map
- Add MagicReveal component (interactive before/after slider)
- Update Navbar with real phone number (06 04 40 81 57)
- Update DemosLive cards with new titles and subtitles
- Create sitemap.ts targeting local SEO zones (Douai, Orchies,
  Valenciennes, Saint-Amand, Arleux, Denain)
- Create LocalSeoPage template + 6 city-specific landing pages

https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
This commit is contained in:
Claude
2026-02-15 22:10:14 +00:00
parent a845b47316
commit 9025986e66
17 changed files with 1331 additions and 259 deletions

View File

@@ -3,11 +3,11 @@ import Link from "next/link";
const demos = [
{
title: "Le Pack \u00ab\u00a0Gros \u0152uvre\u00a0\u00bb",
subtitle: "Ma\u00e7on / Couvreur",
title: "L\u2019Expertise Solide",
subtitle: "Pour ceux dont le travail doit durer 100 ans.",
pourQui: "Ma\u00e7ons, Couvreurs, Charpentiers.",
pointFort: "La galerie \u00ab\u00a0Avant / Apr\u00e8s\u00a0\u00bb qui prouve votre technique et justifie vos devis.",
fonctionnalite: "Bouton \u00ab\u00a0Urgence Fuite\u00a0\u00bb qui d\u00e9clenche l\u2019appel imm\u00e9diat.",
pointFort: "Slider \u00ab\u00a0Avant / Apr\u00e8s\u00a0\u00bb interactif + badges garanties (D\u00e9cennale, Qualibat, RGE) immanquables.",
fonctionnalite: "Formulaire intelligent : si Urgence Fuite \u2192 bouton rouge \u00ab\u00a0APPELER LE PATRON\u00a0\u00bb.",
cta: "Voir la D\u00e9mo Ma\u00e7onnerie",
href: "/macon",
icon: (
@@ -17,11 +17,11 @@ const demos = [
),
},
{
title: "Le Pack \u00ab\u00a0Esth\u00e9tique\u00a0\u00bb",
subtitle: "Paysagiste / Peintre",
title: "L\u2019Artisan Cr\u00e9ateur",
subtitle: "Pour ceux qui vendent du beau et du confort.",
pourQui: "Paysagistes, Peintres, D\u00e9corateurs.",
pointFort: "Un design \u00e9pur\u00e9 qui laisse toute la place \u00e0 la beaut\u00e9 de vos r\u00e9alisations.",
fonctionnalite: "Filtrage \u00ab\u00a0Cr\u00e9ation vs Entretien\u00a0\u00bb pour ne recevoir que les projets \u00e0 forte valeur.",
pointFort: "Galerie filtrable par type + saisonnalit\u00e9 intelligente (le site change selon la saison).",
fonctionnalite: "Bouton WhatsApp flottant \u00ab\u00a0Je veux le m\u00eame jardin\u00a0\u00bb + immersion locale par ville.",
cta: "Voir la D\u00e9mo Paysagiste",
href: "/paysagiste",
icon: (
@@ -31,11 +31,11 @@ const demos = [
),
},
{
title: "Le Pack \u00ab\u00a0Urgence & Service\u00a0\u00bb",
subtitle: "Plombier / \u00c9lec",
title: "L\u2019Intervention \u00c9clair",
subtitle: "Pour ceux qui sauvent la mise (et veulent \u00eatre pay\u00e9s vite).",
pourQui: "Plombiers, \u00c9lectriciens, Serruriers.",
pointFort: "Vitesse de chargement \u00e9clair et rassurance imm\u00e9diate (Avis + Tarifs clairs).",
fonctionnalite: "Formulaire de diagnostic rapide pour qualifier la panne avant de vous d\u00e9placer.",
pointFort: "Avis Google en haut + tarifs transparents + bouton d\u2019appel sticky sur mobile.",
fonctionnalite: "Diagnostic en 3 clics : qualifie la panne + d\u00e9tecte si hors zone.",
cta: "Voir la D\u00e9mo Plombier",
href: "/plombier",
icon: (

View File

@@ -0,0 +1,142 @@
import Link from "next/link";
import Button from "@/components/ui/Button";
import Navbar from "@/components/marketing/Navbar";
import Footer from "@/components/marketing/Footer";
interface LocalSeoPageProps {
ville: string;
villeSlug: string;
codePostal: string;
voisines: string[];
}
export default function LocalSeoPage({ ville, codePostal, voisines }: LocalSeoPageProps) {
return (
<main className="min-h-screen">
<Navbar />
{/* Hero local */}
<section className="py-20 md:py-28 bg-navy text-center">
<div className="max-w-4xl mx-auto px-4">
<span className="inline-block px-3 py-1.5 bg-orange/20 border border-orange/30 rounded-full text-orange text-xs font-semibold mb-6">
{ville} ({codePostal}) et environs
</span>
<h1 className="text-3xl sm:text-4xl md:text-5xl font-extrabold text-white leading-tight mb-6">
Cr&eacute;ation de site internet pour{" "}
<span className="text-orange">artisans &agrave; {ville}</span>
</h1>
<p className="text-white/60 text-lg max-w-2xl mx-auto mb-8">
Vous &ecirc;tes artisan &agrave; {ville} ou dans le secteur de {voisines[0]} / {voisines[1]} ?
Je cr&eacute;e votre site web professionnel et votre pr&eacute;sence Google pour g&eacute;n&eacute;rer
des chantiers qualifi&eacute;s. Bas&eacute; &agrave; Flines-lez-Raches, je suis votre voisin.
</p>
<a href="/#contact">
<Button size="lg" className="pulse-glow">
D&Eacute;MARRER MON AUDIT GRATUIT
</Button>
</a>
<p className="mt-4 text-white/40 text-sm">
R&eacute;ponse sous 24h &middot; 100% gratuit &middot; Sans engagement
</p>
</div>
</section>
{/* M\u00e9tiers couverts */}
<section className="py-16 md:py-24 bg-bg">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl md:text-3xl font-bold text-navy text-center mb-10">
Sites web pour <span className="text-orange">tous les m&eacute;tiers du b&acirc;timent</span> &agrave; {ville}
</h2>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
{[
"Couvreur", "Ma\u00e7on", "Paysagiste",
"Plombier", "\u00c9lectricien", "Menuisier",
"Charpentier", "Peintre", "Serrurier",
].map((metier) => (
<div key={metier} className="bg-bg-white border border-border rounded-xl p-4 text-center hover:shadow-md transition-shadow">
<p className="text-navy font-semibold text-sm">Site internet {metier}</p>
<p className="text-text-muted text-xs mt-1">{ville} et environs</p>
</div>
))}
</div>
</div>
</section>
{/* Pourquoi HookLab */}
<section className="py-16 md:py-24 bg-bg-white">
<div className="max-w-3xl mx-auto px-4 text-center">
<h2 className="text-2xl md:text-3xl font-bold text-navy mb-6">
Pourquoi choisir <span className="text-orange">HookLab</span> &agrave; {ville} ?
</h2>
<div className="space-y-4 text-left">
{[
{
title: "Proximit\u00e9 locale",
desc: `Bas\u00e9 \u00e0 Flines-lez-Raches, je connais ${ville} et ses artisans. On peut se voir en vrai.`,
},
{
title: "Technologie des g\u00e9ants",
desc: "Sites ultra-rapides avec la m\u00eame technologie que Netflix. Google adore la vitesse.",
},
{
title: "R\u00e9sultats concrets",
desc: "Pas un site pour faire joli. Un syst\u00e8me qui fait sonner votre t\u00e9l\u00e9phone avec des vrais clients.",
},
].map((item, i) => (
<div key={i} className="flex items-start gap-4 bg-bg border border-border rounded-xl p-5">
<div className="w-8 h-8 bg-orange/10 rounded-full flex items-center justify-center shrink-0 mt-0.5">
<svg className="w-4 h-4 text-orange" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
</svg>
</div>
<div>
<p className="text-navy font-bold text-base mb-1">{item.title}</p>
<p className="text-text-light text-sm leading-relaxed">{item.desc}</p>
</div>
</div>
))}
</div>
</div>
</section>
{/* D\u00e9mos */}
<section className="py-16 md:py-24 bg-bg">
<div className="max-w-4xl mx-auto px-4 text-center">
<h2 className="text-2xl md:text-3xl font-bold text-navy mb-6">
Testez nos <span className="text-orange">mod&egrave;les</span>
</h2>
<div className="flex flex-wrap gap-4 justify-center">
<Link href="/macon" className="bg-navy text-white font-bold text-sm px-6 py-3 rounded-xl hover:bg-navy/90 transition-colors">
D&eacute;mo Ma&ccedil;on / Couvreur
</Link>
<Link href="/paysagiste" className="bg-green-600 text-white font-bold text-sm px-6 py-3 rounded-xl hover:bg-green-700 transition-colors">
D&eacute;mo Paysagiste
</Link>
<Link href="/plombier" className="bg-[#3b82f6] text-white font-bold text-sm px-6 py-3 rounded-xl hover:bg-[#2563eb] transition-colors">
D&eacute;mo Plombier
</Link>
</div>
</div>
</section>
{/* Zone */}
<section className="py-16 bg-navy text-center">
<div className="max-w-2xl mx-auto px-4">
<h2 className="text-2xl font-bold text-white mb-4">
Aussi disponible &agrave; {voisines.join(", ")}
</h2>
<p className="text-white/60 mb-6">
Je travaille avec des artisans dans tout le Douaisis, l&rsquo;Orch&eacute;sien et le Valenciennois.
</p>
<a href="/#contact">
<Button size="lg" className="pulse-glow">
R&eacute;server Mon Audit Gratuit
</Button>
</a>
</div>
</section>
<Footer />
</main>
);
}

View File

@@ -36,13 +36,13 @@ export default function Navbar() {
{/* CTA desktop - Phone */}
<div className="hidden md:block">
<a
href="tel:+33600000000"
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"
>
<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 XX XX XX XX
06 04 40 81 57
</a>
</div>
@@ -78,14 +78,14 @@ export default function Navbar() {
Qui suis-je
</a>
<a
href="tel:+33600000000"
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 XX XX XX XX
06 04 40 81 57
</a>
</div>
)}

View File

@@ -0,0 +1,91 @@
"use client";
import { useState, useRef, useCallback } from "react";
interface MagicRevealProps {
avantLabel: string;
apresLabel: string;
avantColor?: string;
apresColor?: string;
height?: string;
}
export default function MagicReveal({
avantLabel,
apresLabel,
avantColor = "bg-red-50",
apresColor = "bg-green-50",
height = "h-64",
}: MagicRevealProps) {
const [position, setPosition] = useState(50);
const containerRef = useRef<HTMLDivElement>(null);
const dragging = useRef(false);
const updatePosition = useCallback((clientX: number) => {
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = clientX - rect.left;
const percent = Math.max(0, Math.min(100, (x / rect.width) * 100));
setPosition(percent);
}, []);
const handlePointerDown = useCallback((e: React.PointerEvent) => {
dragging.current = true;
(e.target as HTMLElement).setPointerCapture(e.pointerId);
updatePosition(e.clientX);
}, [updatePosition]);
const handlePointerMove = useCallback((e: React.PointerEvent) => {
if (!dragging.current) return;
updatePosition(e.clientX);
}, [updatePosition]);
const handlePointerUp = useCallback(() => {
dragging.current = false;
}, []);
return (
<div
ref={containerRef}
className={`relative ${height} rounded-xl overflow-hidden cursor-col-resize select-none border border-border`}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
>
{/* Apr\u00e8s (fond complet) */}
<div className={`absolute inset-0 ${apresColor} flex items-center justify-center`}>
<div className="text-center">
<p className="text-green-600 font-bold text-sm uppercase tracking-wider mb-1">Apr\u00e8s</p>
<p className="text-green-800 text-sm font-medium px-4">{apresLabel}</p>
</div>
</div>
{/* Avant (clip\u00e9 \u00e0 gauche) */}
<div
className={`absolute inset-0 ${avantColor} flex items-center justify-center`}
style={{ clipPath: `inset(0 ${100 - position}% 0 0)` }}
>
<div className="text-center">
<p className="text-red-600 font-bold text-sm uppercase tracking-wider mb-1">Avant</p>
<p className="text-red-800 text-sm font-medium px-4">{avantLabel}</p>
</div>
</div>
{/* Barre de s\u00e9paration */}
<div
className="absolute top-0 bottom-0 w-1 bg-white shadow-lg z-10"
style={{ left: `${position}%`, transform: "translateX(-50%)" }}
>
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-10 h-10 bg-white rounded-full shadow-lg flex items-center justify-center border-2 border-orange">
<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="M8 9l4-4 4 4m0 6l-4 4-4-4" />
</svg>
</div>
</div>
{/* Labels gauche/droite */}
<div className="absolute top-2 left-3 bg-red-600 text-white text-[10px] font-bold px-2 py-0.5 rounded-full z-20">AVANT</div>
<div className="absolute top-2 right-3 bg-green-600 text-white text-[10px] font-bold px-2 py-0.5 rounded-full z-20">APR\u00c8S</div>
</div>
);
}