Update application features and add scripts (core changes)
This commit is contained in:
+133
@@ -0,0 +1,133 @@
|
||||
import fs from 'fs'
|
||||
|
||||
const content = `'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'
|
||||
import openinaryLoader from '@/lib/openinary-loader'
|
||||
|
||||
export default function GalleryPage() {
|
||||
const t = useTranslations('gallery')
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null)
|
||||
|
||||
const galleryImages = [
|
||||
'DSC01477.jpg',
|
||||
'DSC01478.jpg',
|
||||
'DSC01479.jpg',
|
||||
'DSC01480.jpg',
|
||||
'DSC01481.jpg',
|
||||
'DSC01482.jpg',
|
||||
'DSC01483.jpg',
|
||||
'DSC01487.jpg',
|
||||
'DSC01535.jpg',
|
||||
'DSC01544.jpg',
|
||||
'DSC01554.jpg',
|
||||
'DSC01560.jpg',
|
||||
'DSC01563.jpg'
|
||||
]
|
||||
|
||||
const galleryItems = galleryImages.map((file, idx) => ({
|
||||
id: idx + 1,
|
||||
title: t('items.' + ((idx % 6) + 1)), // Use existing translations just to not break them
|
||||
src: \`https://media.ayris.tech/t/starapart/gallery/\${file}\`,
|
||||
colSpan: idx % 4 === 1 ? 'col-span-1 md:col-span-2 lg:col-span-1' : 'col-span-1'
|
||||
}))
|
||||
|
||||
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>
|
||||
|
||||
{/* Masonry-like Grid */}
|
||||
<motion.div layout className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<AnimatePresence>
|
||||
{galleryItems.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
|
||||
loader={openinaryLoader}
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
className="object-cover transform group-hover:scale-105 transition-transform duration-700"
|
||||
/>
|
||||
</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
|
||||
loader={openinaryLoader}
|
||||
className="object-contain"
|
||||
sizes="100vw"
|
||||
quality={90}
|
||||
/>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
`
|
||||
|
||||
fs.writeFileSync('app/[locale]/galeri/page.tsx', content)
|
||||
Reference in New Issue
Block a user