update ads
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
// Mapping des textes associés à chaque visuel
|
||||||
const ADS_COPY: Record<string, { title: string; subtitle: string }> = {
|
const ADS_COPY: Record<string, { title: string; subtitle: string }> = {
|
||||||
"riseofkinkdom.jpeg": {
|
"riseofkinkdom.jpeg": {
|
||||||
title: "Atteignez 50 Millions d’Impuissance !",
|
title: "Atteignez 50 Millions d’Impuissance !",
|
||||||
@@ -15,9 +17,35 @@ const ADS_COPY: Record<string, { title: string; subtitle: string }> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function AdsGalleryClient({ files }: { files: string[] }) {
|
export default function AdsGalleryClient({ files }: { files: string[] }) {
|
||||||
|
// \u2714️ État qui détermine l'image agrandie actuellement affichée
|
||||||
|
const [selected, setSelected] = useState<string | null>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gestion du clavier : ␛ pour fermer la light-box
|
||||||
|
* On utilise le « KeyboardEvent » natif du DOM, plus compatible
|
||||||
|
*/
|
||||||
|
const handleKey = useCallback((e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") setSelected(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selected) return; // rien à faire si aucune image n'est ouverte
|
||||||
|
|
||||||
|
// ➜ attache l'écouteur
|
||||||
|
window.addEventListener("keydown", handleKey);
|
||||||
|
document.body.style.overflow = "hidden"; // bloque le scroll arrière-plan
|
||||||
|
|
||||||
|
// ➜ cleanup à la fermeture / unmount
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", handleKey);
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
};
|
||||||
|
}, [selected, handleKey]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-screen flex-col bg-[#0e0e10]">
|
<main className="flex min-h-screen flex-col bg-[#0e0e10]">
|
||||||
<section className="mx-auto w-full max-w-6xl flex-1 px-4 py-12">
|
<section className="mx-auto w-full max-w-6xl flex-1 px-4 py-12">
|
||||||
|
{/* Titre */}
|
||||||
<motion.h1
|
<motion.h1
|
||||||
initial={{ opacity: 0, y: 10 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
@@ -27,6 +55,7 @@ export default function AdsGalleryClient({ files }: { files: string[] }) {
|
|||||||
Galerie des publicités
|
Galerie des publicités
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
|
|
||||||
|
{/* Message si aucun fichier */}
|
||||||
{files.length === 0 ? (
|
{files.length === 0 ? (
|
||||||
<motion.p
|
<motion.p
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
@@ -34,8 +63,7 @@ export default function AdsGalleryClient({ files }: { files: string[] }) {
|
|||||||
transition={{ delay: 0.2 }}
|
transition={{ delay: 0.2 }}
|
||||||
className="text-gray-400"
|
className="text-gray-400"
|
||||||
>
|
>
|
||||||
Aucune bannière trouvée dans{" "}
|
Aucune bannière trouvée dans <code className="text-sm">/public/ads</code>.
|
||||||
<code className="text-sm">/public/ads</code>.
|
|
||||||
</motion.p>
|
</motion.p>
|
||||||
) : (
|
) : (
|
||||||
<motion.ul
|
<motion.ul
|
||||||
@@ -43,11 +71,7 @@ export default function AdsGalleryClient({ files }: { files: string[] }) {
|
|||||||
animate="visible"
|
animate="visible"
|
||||||
variants={{
|
variants={{
|
||||||
hidden: {},
|
hidden: {},
|
||||||
visible: {
|
visible: { transition: { staggerChildren: 0.1 } },
|
||||||
transition: {
|
|
||||||
staggerChildren: 0.1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"
|
className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"
|
||||||
>
|
>
|
||||||
@@ -56,23 +80,23 @@ export default function AdsGalleryClient({ files }: { files: string[] }) {
|
|||||||
return (
|
return (
|
||||||
<motion.li
|
<motion.li
|
||||||
key={file}
|
key={file}
|
||||||
variants={{
|
variants={{ hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 } }}
|
||||||
hidden: { opacity: 0, y: 20 },
|
className="cursor-pointer overflow-hidden rounded-xl border border-[#1c2536] bg-[#0b1320] shadow"
|
||||||
visible: { opacity: 1, y: 0 },
|
onClick={() => setSelected(file)}
|
||||||
}}
|
|
||||||
className="overflow-hidden rounded-xl border border-[#1c2536] bg-[#0b1320] shadow"
|
|
||||||
>
|
>
|
||||||
|
{/* Vignette */}
|
||||||
<div className="relative h-42 w-full">
|
<div className="relative h-42 w-full">
|
||||||
<Image
|
<Image
|
||||||
src={`/ads/${file}`}
|
src={`/ads/${file}`}
|
||||||
alt={file}
|
alt={file}
|
||||||
fill
|
fill
|
||||||
className="object-cover"
|
className="object-cover transition-transform duration-200 hover:scale-105"
|
||||||
sizes="(min-width:1024px) 33vw, (min-width:640px) 50vw, 100vw"
|
sizes="(min-width:1024px) 33vw, (min-width:640px) 50vw, 100vw"
|
||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Texte associé */}
|
||||||
{copy && (
|
{copy && (
|
||||||
<div className="px-4 py-3 text-center">
|
<div className="px-4 py-3 text-center">
|
||||||
<h2 className="text-[15px] font-bold leading-snug text-amber-100">
|
<h2 className="text-[15px] font-bold leading-snug text-amber-100">
|
||||||
@@ -86,11 +110,11 @@ export default function AdsGalleryClient({ files }: { files: string[] }) {
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-1 text-sm text-gray-300">
|
<p className="mt-1 text-sm text-gray-300">{copy.subtitle}</p>
|
||||||
{copy.subtitle}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Nom de fichier */}
|
||||||
<p className="truncate bg-[#0b1320] px-3 py-2 text-center text-xs text-gray-500">
|
<p className="truncate bg-[#0b1320] px-3 py-2 text-center text-xs text-gray-500">
|
||||||
{file}
|
{file}
|
||||||
</p>
|
</p>
|
||||||
@@ -100,6 +124,48 @@ export default function AdsGalleryClient({ files }: { files: string[] }) {
|
|||||||
</motion.ul>
|
</motion.ul>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* ---- Light-box plein écran ---- */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{selected && (
|
||||||
|
<motion.div
|
||||||
|
key="overlay"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.25 }}
|
||||||
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-md"
|
||||||
|
onClick={(e) => {
|
||||||
|
// Ferme si on clique en dehors de l'image
|
||||||
|
if (e.target === e.currentTarget) setSelected(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0.9, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
exit={{ scale: 0.9, opacity: 0 }}
|
||||||
|
className="relative max-h-[90vh] max-w-[90vw]"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={`/ads/${selected}`}
|
||||||
|
alt={selected}
|
||||||
|
width={1200}
|
||||||
|
height={800}
|
||||||
|
className="h-auto w-auto rounded-lg shadow-xl"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
{/* Bouton de fermeture */}
|
||||||
|
<button
|
||||||
|
aria-label="Fermer"
|
||||||
|
className="absolute right-2 top-2 rounded-full bg-black/60 p-1 text-white hover:bg-black/80 focus:outline-none"
|
||||||
|
onClick={() => setSelected(null)}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user