183 lines
6.8 KiB
TypeScript
183 lines
6.8 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
import Image from 'next/image'
|
|
import BougainvilleaMotif from '@/components/BougainvilleaMotif'
|
|
import { useTranslations } from 'next-intl'
|
|
import { X } from 'lucide-react'
|
|
|
|
export default function GalleryPage() {
|
|
const t = useTranslations('gallery')
|
|
const [activeFilter, setActiveFilter] = useState('all')
|
|
const [selectedImage, setSelectedImage] = useState<string | null>(null)
|
|
|
|
const filters = [
|
|
{ id: 'all', label: t('filters.all') },
|
|
{ id: 'rooms', label: t('filters.rooms') },
|
|
{ id: 'exterior', label: t('filters.exterior') },
|
|
{ id: 'view', label: t('filters.view') },
|
|
{ id: 'balcony', label: t('filters.balcony') },
|
|
]
|
|
|
|
const galleryItems = [
|
|
{
|
|
id: 1,
|
|
category: 'rooms',
|
|
title: t('items.1'),
|
|
src: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80',
|
|
colSpan: 'col-span-1',
|
|
},
|
|
{
|
|
id: 2,
|
|
category: 'exterior',
|
|
title: t('items.2'),
|
|
src: 'https://images.unsplash.com/photo-1596394516093-501ba68a0ba6?auto=format&fit=crop&q=80',
|
|
colSpan: 'col-span-1 md:col-span-2 lg:col-span-1',
|
|
},
|
|
{
|
|
id: 3,
|
|
category: 'view',
|
|
title: t('items.3'),
|
|
src: 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&q=80',
|
|
colSpan: 'col-span-1',
|
|
},
|
|
{
|
|
id: 4,
|
|
category: 'balcony',
|
|
title: t('items.4'),
|
|
src: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&q=80',
|
|
colSpan: 'col-span-1 md:col-span-2',
|
|
},
|
|
{
|
|
id: 5,
|
|
category: 'rooms',
|
|
title: t('items.5'),
|
|
src: 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?auto=format&fit=crop&q=80',
|
|
colSpan: 'col-span-1',
|
|
},
|
|
{
|
|
id: 6,
|
|
category: 'view',
|
|
title: t('items.6'),
|
|
src: 'https://images.unsplash.com/photo-1582719508461-905c673771fd?auto=format&fit=crop&q=80',
|
|
colSpan: 'col-span-1 md:col-span-2 lg:col-span-1',
|
|
},
|
|
]
|
|
|
|
const filteredItems = galleryItems.filter(item =>
|
|
activeFilter === 'all' || item.category === activeFilter
|
|
)
|
|
|
|
return (
|
|
<div className="bg-background min-h-screen text-on-surface pt-32 pb-section-gap container mx-auto px-4">
|
|
{/* Header Section */}
|
|
<header className="relative mb-16 max-w-2xl">
|
|
<div className="absolute -top-6 -left-4 opacity-80 -rotate-6 pointer-events-none text-accent-pink">
|
|
<BougainvilleaMotif className="w-16 h-16" />
|
|
</div>
|
|
<h1 className="font-serif text-[40px] md:text-[48px] font-semibold text-secondary mb-4 relative z-10">
|
|
{t('title')}
|
|
</h1>
|
|
<p className="font-sans text-[18px] text-on-surface-variant max-w-xl">
|
|
{t('description')}
|
|
</p>
|
|
</header>
|
|
|
|
{/* Filter Bar */}
|
|
<div className="flex flex-wrap items-center gap-4 mb-12">
|
|
{filters.map((filter) => (
|
|
<button
|
|
key={filter.id}
|
|
onClick={() => setActiveFilter(filter.id)}
|
|
className={`px-6 py-2 rounded-full font-sans font-bold text-[14px] uppercase tracking-wider transition-all duration-300 border-2 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primary ${
|
|
activeFilter === filter.id
|
|
? 'border-primary text-primary bg-primary/10'
|
|
: 'border-transparent text-secondary hover:border-primary/30'
|
|
}`}
|
|
>
|
|
{filter.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Masonry-like Grid */}
|
|
<motion.div layout className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<AnimatePresence>
|
|
{filteredItems.map((item) => (
|
|
<motion.div
|
|
key={item.id}
|
|
layout
|
|
initial={{ opacity: 0, scale: 0.9 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
exit={{ opacity: 0, scale: 0.9 }}
|
|
transition={{ duration: 0.4 }}
|
|
className={`group cursor-pointer focus:outline-none focus-visible:ring-4 focus-visible:ring-primary rounded-[24px] ${item.colSpan}`}
|
|
onClick={() => setSelectedImage(item.src)}
|
|
tabIndex={0}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
setSelectedImage(item.src);
|
|
}
|
|
}}
|
|
>
|
|
<div className="relative overflow-hidden rounded-[24px] bg-surface-container shadow-sm hover:shadow-xl transition-all duration-500 aspect-[4/3] md:aspect-auto md:h-80">
|
|
<Image
|
|
src={item.src}
|
|
alt={item.title}
|
|
fill
|
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
|
className="object-cover transform group-hover:scale-105 transition-transform duration-700"
|
|
/>
|
|
<div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity flex items-end p-6">
|
|
<span className="text-white font-serif text-[24px] font-medium drop-shadow-md">
|
|
{item.title}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</AnimatePresence>
|
|
</motion.div>
|
|
|
|
{/* Lightbox Overlay */}
|
|
<AnimatePresence>
|
|
{selectedImage && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4 md:p-8 backdrop-blur-sm"
|
|
onClick={() => setSelectedImage(null)}
|
|
>
|
|
<button
|
|
aria-label="Close lightbox"
|
|
className="absolute top-6 right-6 text-white/70 hover:text-white transition-colors duration-300 z-50 p-2 bg-black/20 rounded-full cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-white"
|
|
onClick={() => setSelectedImage(null)}
|
|
>
|
|
<X className="w-8 h-8" />
|
|
</button>
|
|
<motion.div
|
|
initial={{ scale: 0.9, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
exit={{ scale: 0.9, opacity: 0 }}
|
|
className="relative w-full max-w-5xl h-[80vh] bg-transparent rounded-lg overflow-hidden"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<Image
|
|
src={selectedImage}
|
|
alt="Enlarged view"
|
|
fill
|
|
className="object-contain"
|
|
sizes="100vw"
|
|
quality={90}
|
|
/>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
)
|
|
}
|