fix: supprime social proof fake, fixe bannière + navbar, popup mobile
- Supprime SocialProofTicker (notifications fake visiblement artificielles) - Navbar passe de fixed à sticky pour s'empiler correctement sous la bannière - Bannière d'annonce responsive (texte court sur mobile, bouton close adapté) - Hero: réduit le padding top (plus besoin de compenser un navbar fixed) - Exit-intent popup fonctionne sur mobile (trigger au scroll-up après 60% de page) - Popup responsive: tailles ajustées pour mobile https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
This commit is contained in:
@@ -11,7 +11,6 @@ import TrustBadges from "@/components/marketing/TrustBadges";
|
|||||||
import FAQ from "@/components/marketing/FAQ";
|
import FAQ from "@/components/marketing/FAQ";
|
||||||
import FinalCTA from "@/components/marketing/FinalCTA";
|
import FinalCTA from "@/components/marketing/FinalCTA";
|
||||||
import Footer from "@/components/marketing/Footer";
|
import Footer from "@/components/marketing/Footer";
|
||||||
import SocialProofTicker from "@/components/marketing/SocialProofTicker";
|
|
||||||
import ExitIntentPopup from "@/components/marketing/ExitIntentPopup";
|
import ExitIntentPopup from "@/components/marketing/ExitIntentPopup";
|
||||||
import StickyMobileCTA from "@/components/marketing/StickyMobileCTA";
|
import StickyMobileCTA from "@/components/marketing/StickyMobileCTA";
|
||||||
|
|
||||||
@@ -59,10 +58,7 @@ export default function LandingPage() {
|
|||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|
||||||
{/* Social proof notifications (bottom left) */}
|
{/* Exit intent popup */}
|
||||||
<SocialProofTicker />
|
|
||||||
|
|
||||||
{/* Exit intent popup (desktop only) */}
|
|
||||||
<ExitIntentPopup />
|
<ExitIntentPopup />
|
||||||
|
|
||||||
{/* Sticky mobile CTA bar */}
|
{/* Sticky mobile CTA bar */}
|
||||||
|
|||||||
@@ -9,18 +9,28 @@ export default function AnnouncementBar() {
|
|||||||
if (!visible) return null;
|
if (!visible) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative bg-gradient-to-r from-primary via-primary-hover to-primary text-white text-center py-2.5 px-4 text-sm font-medium z-[60]">
|
<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">
|
<Link href="/candidature" className="hover:underline">
|
||||||
Places limitées — Nouvelle session de formation TikTok Shop
|
<span className="hidden sm:inline">
|
||||||
ouverte → <span className="underline font-bold">Candidater</span>
|
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>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => setVisible(false)}
|
onClick={(e) => {
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-white/70 hover:text-white cursor-pointer"
|
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"
|
aria-label="Fermer"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,33 +1,68 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Button from "@/components/ui/Button";
|
import Button from "@/components/ui/Button";
|
||||||
|
|
||||||
export default function ExitIntentPopup() {
|
export default function ExitIntentPopup() {
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
const [dismissed, setDismissed] = useState(false);
|
const [dismissed, setDismissed] = useState(false);
|
||||||
|
const lastScrollY = useRef(0);
|
||||||
|
const maxScrollY = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dismissed) return;
|
if (dismissed) return;
|
||||||
|
|
||||||
// Check if already shown this session
|
// Check if already shown this session
|
||||||
if (sessionStorage.getItem("hooklab_exit_popup")) return;
|
if (typeof window !== "undefined" && sessionStorage.getItem("hooklab_exit_popup")) {
|
||||||
|
setDismissed(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const handleMouseLeave = (e: MouseEvent) => {
|
const triggerPopup = () => {
|
||||||
if (e.clientY <= 5 && !show && !dismissed) {
|
if (!show && !dismissed) {
|
||||||
setShow(true);
|
setShow(true);
|
||||||
sessionStorage.setItem("hooklab_exit_popup", "1");
|
sessionStorage.setItem("hooklab_exit_popup", "1");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only on desktop
|
// Desktop: mouse leaves viewport at top
|
||||||
if (window.innerWidth >= 768) {
|
const handleMouseLeave = (e: MouseEvent) => {
|
||||||
document.addEventListener("mouseleave", handleMouseLeave);
|
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 () => {
|
return () => {
|
||||||
document.removeEventListener("mouseleave", handleMouseLeave);
|
document.removeEventListener("mouseleave", handleMouseLeave);
|
||||||
|
window.removeEventListener("scroll", handleScroll);
|
||||||
};
|
};
|
||||||
}, [show, dismissed]);
|
}, [show, dismissed]);
|
||||||
|
|
||||||
@@ -47,7 +82,7 @@ export default function ExitIntentPopup() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
<div className="relative bg-dark-light border border-dark-border rounded-3xl p-8 max-w-md w-full shadow-2xl animate-scale-in">
|
<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 */}
|
{/* Close */}
|
||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
@@ -61,28 +96,29 @@ export default function ExitIntentPopup() {
|
|||||||
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
{/* Icon */}
|
{/* Icon */}
|
||||||
<div className="w-16 h-16 gradient-bg rounded-full flex items-center justify-center mx-auto mb-5">
|
<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-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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" />
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="text-2xl font-bold text-white mb-3">
|
<h3 className="text-xl sm:text-2xl font-bold text-white mb-2 sm:mb-3">
|
||||||
Attends ! Tu passes à côté d'une opportunité
|
Tu hésites encore ?
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-white/60 text-sm mb-6 leading-relaxed">
|
<p className="text-white/60 text-sm mb-5 sm:mb-6 leading-relaxed">
|
||||||
TikTok Shop vient d'arriver en France et le marché n'est pas encore saturé.
|
TikTok Shop vient d'arriver en France. Le marché n'est pas
|
||||||
Les premiers créateurs sont ceux qui gagnent le plus. Ne laisse pas passer ta chance.
|
encore saturé et les premiers créateurs captent
|
||||||
|
l'essentiel des commissions.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
<div className="grid grid-cols-2 gap-3 mb-6">
|
<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">
|
<div className="bg-dark border border-dark-border rounded-xl p-3">
|
||||||
<p className="text-xl font-bold gradient-text">50,5M€</p>
|
<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>
|
<p className="text-white/40 text-xs">Marché FR en 2 mois</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-dark border border-dark-border rounded-xl p-3">
|
<div className="bg-dark border border-dark-border rounded-xl p-3">
|
||||||
<p className="text-xl font-bold gradient-text">10-30%</p>
|
<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>
|
<p className="text-white/40 text-xs">Commission par vente</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,7 +132,7 @@ export default function ExitIntentPopup() {
|
|||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
className="text-white/30 text-xs hover:text-white/50 transition-colors cursor-pointer"
|
className="text-white/30 text-xs hover:text-white/50 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
Non merci, je préfère me lancer seul
|
Non merci
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Button from "@/components/ui/Button";
|
|||||||
|
|
||||||
export default function Hero() {
|
export default function Hero() {
|
||||||
return (
|
return (
|
||||||
<section className="relative pt-32 pb-20 md:pt-40 md:pb-32 overflow-hidden">
|
<section className="relative pt-16 pb-20 md:pt-24 md:pb-32 overflow-hidden">
|
||||||
{/* Gradient background effect */}
|
{/* Gradient background effect */}
|
||||||
<div className="absolute inset-0 overflow-hidden">
|
<div className="absolute inset-0 overflow-hidden">
|
||||||
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 w-[800px] h-[400px] bg-primary/20 rounded-full blur-[120px]" />
|
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 w-[800px] h-[400px] bg-primary/20 rounded-full blur-[120px]" />
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default function Navbar() {
|
|||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="fixed top-0 left-0 right-0 z-50 glass">
|
<nav className="sticky top-0 z-50 glass">
|
||||||
<div className="max-w-7xl 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 md:h-20">
|
<div className="flex items-center justify-between h-16 md:h-20">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
@@ -27,13 +27,13 @@ export default function Navbar() {
|
|||||||
href="#methode"
|
href="#methode"
|
||||||
className="text-white/70 hover:text-white transition-colors text-sm font-medium"
|
className="text-white/70 hover:text-white transition-colors text-sm font-medium"
|
||||||
>
|
>
|
||||||
Methode
|
Méthode
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#temoignages"
|
href="#temoignages"
|
||||||
className="text-white/70 hover:text-white transition-colors text-sm font-medium"
|
className="text-white/70 hover:text-white transition-colors text-sm font-medium"
|
||||||
>
|
>
|
||||||
Resultats
|
Résultats
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#tarif"
|
href="#tarif"
|
||||||
@@ -101,14 +101,14 @@ export default function Navbar() {
|
|||||||
className="text-white/70 hover:text-white transition-colors text-sm font-medium"
|
className="text-white/70 hover:text-white transition-colors text-sm font-medium"
|
||||||
onClick={() => setIsOpen(false)}
|
onClick={() => setIsOpen(false)}
|
||||||
>
|
>
|
||||||
Methode
|
Méthode
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#temoignages"
|
href="#temoignages"
|
||||||
className="text-white/70 hover:text-white transition-colors text-sm font-medium"
|
className="text-white/70 hover:text-white transition-colors text-sm font-medium"
|
||||||
onClick={() => setIsOpen(false)}
|
onClick={() => setIsOpen(false)}
|
||||||
>
|
>
|
||||||
Resultats
|
Résultats
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#tarif"
|
href="#tarif"
|
||||||
|
|||||||
Reference in New Issue
Block a user