first commit
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Instagram } from '@/components/InstagramIcon';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { X, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
|
||||
export default function Gallery() {
|
||||
const t = useTranslations('Gallery');
|
||||
|
||||
const images = Array.from({ length: 12 }).map((_, i) => ({
|
||||
id: i,
|
||||
src: `https://picsum.photos/600/${400 + (i % 5) * 50}?random=${30 + i}`,
|
||||
alt: `Gallery ${i + 1}`,
|
||||
}));
|
||||
|
||||
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
||||
|
||||
// Close lightbox on escape key
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') setSelectedIndex(null);
|
||||
if (e.key === 'ArrowRight' && selectedIndex !== null) {
|
||||
setSelectedIndex((prev) => (prev !== null ? (prev + 1) % images.length : null));
|
||||
}
|
||||
if (e.key === 'ArrowLeft' && selectedIndex !== null) {
|
||||
setSelectedIndex((prev) => (prev !== null ? (prev - 1 + images.length) % images.length : null));
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [selectedIndex, images.length]);
|
||||
|
||||
return (
|
||||
<section id="gallery" className="py-24 bg-white text-midnight">
|
||||
<div className="container mx-auto px-6 lg:px-12">
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-end mb-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<span className="text-xs tracking-[0.4em] uppercase font-black text-coral mb-3 block">
|
||||
— Fotoğraflar
|
||||
</span>
|
||||
<h2 className="font-heading text-5xl md:text-6xl font-black text-midnight">
|
||||
{t('title')}
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<motion.a
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
href="https://www.instagram.com/kozmosbeach/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 px-5 py-2.5 rounded-full border border-coral/30 text-coral font-bold text-sm hover:bg-coral hover:text-white transition-all"
|
||||
>
|
||||
<Instagram size={15} />
|
||||
{t('follow_instagram')}
|
||||
</motion.a>
|
||||
</div>
|
||||
|
||||
{/* Masonry grid */}
|
||||
<div className="columns-1 sm:columns-2 md:columns-3 lg:columns-4 gap-3 space-y-3">
|
||||
{images.map((img, index) => (
|
||||
<motion.div
|
||||
key={img.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: (index % 4) * 0.07 }}
|
||||
onClick={() => setSelectedIndex(index)}
|
||||
className="relative rounded-2xl overflow-hidden group break-inside-avoid cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src={img.src}
|
||||
alt={img.alt}
|
||||
className="w-full h-auto object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
loading="lazy"
|
||||
/>
|
||||
{/* Coral gradient overlay */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(135deg, rgba(255,90,54,0.55), rgba(0,191,165,0.4))',
|
||||
}}
|
||||
/>
|
||||
{/* Plus icon */}
|
||||
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<span className="text-white text-4xl font-thin select-none">+</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Lightbox Overlay */}
|
||||
<AnimatePresence>
|
||||
{selectedIndex !== null && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-midnight/95 backdrop-blur-md"
|
||||
>
|
||||
{/* Close Button */}
|
||||
<button
|
||||
onClick={() => setSelectedIndex(null)}
|
||||
className="absolute top-6 right-6 z-[110] text-white/70 hover:text-white transition-colors"
|
||||
>
|
||||
<X size={36} />
|
||||
</button>
|
||||
|
||||
{/* Left/Right Navigation */}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedIndex((selectedIndex - 1 + images.length) % images.length);
|
||||
}}
|
||||
className="absolute left-4 md:left-10 z-[110] p-3 rounded-full bg-white/10 text-white hover:bg-white/20 transition-all"
|
||||
>
|
||||
<ChevronLeft size={30} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedIndex((selectedIndex + 1) % images.length);
|
||||
}}
|
||||
className="absolute right-4 md:right-10 z-[110] p-3 rounded-full bg-white/10 text-white hover:bg-white/20 transition-all"
|
||||
>
|
||||
<ChevronRight size={30} />
|
||||
</button>
|
||||
|
||||
{/* Image */}
|
||||
<motion.div
|
||||
key={selectedIndex}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
transition={{ type: "spring", damping: 25, stiffness: 300 }}
|
||||
className="relative max-w-5xl w-full h-[80vh] px-4 md:px-24"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<img
|
||||
src={images[selectedIndex].src}
|
||||
alt={images[selectedIndex].alt}
|
||||
className="w-full h-full object-contain drop-shadow-2xl"
|
||||
/>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user