Files

172 lines
6.0 KiB
TypeScript
Raw Permalink Normal View History

2025-07-29 05:15:33 +02:00
"use client";
2025-07-30 03:27:54 +02:00
import { AnimatePresence, motion } from "framer-motion";
2025-07-29 05:15:33 +02:00
import Image from "next/image";
2025-07-30 03:27:54 +02:00
import { useCallback, useEffect, useState } from "react";
2025-07-29 05:15:33 +02:00
2025-07-30 03:27:54 +02:00
// Mapping des textes associés à chaque visuel
2025-07-29 05:15:33 +02:00
const ADS_COPY: Record<string, { title: string; subtitle: string }> = {
"riseofkinkdom.jpeg": {
title: "Atteignez 50 Millions dImpuissance !",
subtitle: "Rejoignez « Rise of Kink Doms » et dominez vos adversaires.",
},
"voat.png": {
title: "Aymeric Pierre vend votre voiture !",
subtitle: "Offre exclusive : 69,42 BTC, garantie World PvP Champion.",
},
};
export default function AdsGalleryClient({ files }: { files: string[] }) {
2025-07-30 03:27:54 +02:00
// \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]);
2025-07-29 05:15:33 +02:00
return (
<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">
2025-07-30 03:27:54 +02:00
{/* Titre */}
2025-07-29 05:15:33 +02:00
<motion.h1
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, ease: "easeOut" }}
className="mb-10 text-3xl font-extrabold text-red-600"
>
Galerie des publicités
</motion.h1>
2025-07-30 03:27:54 +02:00
{/* Message si aucun fichier */}
2025-07-29 05:15:33 +02:00
{files.length === 0 ? (
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
className="text-gray-400"
>
2025-07-30 03:27:54 +02:00
Aucune bannière trouvée dans <code className="text-sm">/public/ads</code>.
2025-07-29 05:15:33 +02:00
</motion.p>
) : (
<motion.ul
initial="hidden"
animate="visible"
variants={{
hidden: {},
2025-07-30 03:27:54 +02:00
visible: { transition: { staggerChildren: 0.1 } },
2025-07-29 05:15:33 +02:00
}}
className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"
>
{files.map((file) => {
const copy = ADS_COPY[file];
return (
<motion.li
key={file}
2025-07-30 03:27:54 +02:00
variants={{ hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 } }}
className="cursor-pointer overflow-hidden rounded-xl border border-[#1c2536] bg-[#0b1320] shadow"
onClick={() => setSelected(file)}
2025-07-29 05:15:33 +02:00
>
2025-07-30 03:27:54 +02:00
{/* Vignette */}
2025-07-29 05:15:33 +02:00
<div className="relative h-42 w-full">
<Image
src={`/ads/${file}`}
alt={file}
fill
2025-07-30 03:27:54 +02:00
className="object-cover transition-transform duration-200 hover:scale-105"
2025-07-29 05:15:33 +02:00
sizes="(min-width:1024px) 33vw, (min-width:640px) 50vw, 100vw"
priority
/>
</div>
2025-07-30 03:27:54 +02:00
{/* Texte associé */}
2025-07-29 05:15:33 +02:00
{copy && (
<div className="px-4 py-3 text-center">
<h2 className="text-[15px] font-bold leading-snug text-amber-100">
{copy.title.split(/(50 Millions)/).map((part, i) =>
part === "50 Millions" ? (
<span key={i} className="text-red-500">
{part}
</span>
) : (
part
)
)}
</h2>
2025-07-30 03:27:54 +02:00
<p className="mt-1 text-sm text-gray-300">{copy.subtitle}</p>
2025-07-29 05:15:33 +02:00
</div>
)}
2025-07-30 03:27:54 +02:00
{/* Nom de fichier */}
2025-07-29 05:15:33 +02:00
<p className="truncate bg-[#0b1320] px-3 py-2 text-center text-xs text-gray-500">
{file}
</p>
</motion.li>
);
})}
</motion.ul>
)}
</section>
2025-07-30 03:27:54 +02:00
{/* ---- 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>
2025-07-29 05:15:33 +02:00
</main>
);
}