Spaces:
Sleeping
Sleeping
'use client' | |
import { useState } from 'react'; | |
import { cultures } from '@/data/cultures'; | |
import { generateDescription, generateImage } from '@/services/ai'; | |
import { motion, AnimatePresence } from 'framer-motion'; | |
export default function Home() { | |
const [loading, setLoading] = useState(false); | |
const [error, setError] = useState<string | null>(null); | |
const [showPreview, setShowPreview] = useState(false); | |
const [culture, setCulture] = useState<{ | |
name: string; | |
description: string; | |
imageUrl: string; | |
} | null>(null); | |
const generateRandomCulture = async () => { | |
setLoading(true); | |
setError(null); | |
try { | |
const randomCulture = cultures[Math.floor(Math.random() * cultures.length)]; | |
const [description, imageData] = await Promise.all([ | |
generateDescription(randomCulture), | |
generateImage(`Indonesian traditional culture ${randomCulture}, professional photography style, high quality, detailed, 4k resolution`) | |
]); | |
if (!imageData || !imageData.imageUrl) { | |
throw new Error('Failed to generate image'); | |
} | |
setCulture({ | |
name: randomCulture, | |
description: description || '', | |
imageUrl: imageData.imageUrl | |
}); | |
} catch (error) { | |
console.error('Error:', error); | |
setError('Failed to generate content. Please try again.'); | |
} finally { | |
setLoading(false); | |
} | |
}; | |
return ( | |
<main className="min-h-screen p-8 bg-gradient-to-br from-orange-100 to-red-100"> | |
<div className="max-w-4xl mx-auto"> | |
<div className="text-center mb-12"> | |
<motion.h1 | |
initial={{ opacity: 0, y: -20 }} | |
animate={{ opacity: 1, y: 0 }} | |
className="text-5xl font-bold mb-3 text-red-800" | |
> | |
Warisan Nusantara | |
</motion.h1> | |
<motion.p | |
initial={{ opacity: 0, y: -20 }} | |
animate={{ opacity: 1, y: 0 }} | |
transition={{ delay: 0.2 }} | |
className="text-xl text-red-700/80" | |
> | |
Menjelajahi Ragam Warisan Budaya Indonesia | |
</motion.p> | |
</div> | |
<motion.button | |
whileHover={{ scale: 1.02 }} | |
whileTap={{ scale: 0.98 }} | |
onClick={generateRandomCulture} | |
disabled={loading} | |
className="w-full max-w-md mx-auto block px-6 py-3 bg-red-600 text-white rounded-lg shadow-lg hover:bg-red-700 transition-colors disabled:bg-gray-400" | |
> | |
{loading ? ( | |
<div className="flex items-center justify-center"> | |
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-white mr-2"></div> | |
Memuat... | |
</div> | |
) : ( | |
'Jelajahi Budaya' | |
)} | |
</motion.button> | |
{error && ( | |
<motion.div | |
initial={{ opacity: 0 }} | |
animate={{ opacity: 1 }} | |
className="mt-4 p-4 bg-red-100 text-red-700 rounded-lg" | |
> | |
{error} | |
</motion.div> | |
)} | |
<AnimatePresence> | |
{culture && ( | |
<motion.div | |
initial={{ opacity: 0, y: 20 }} | |
animate={{ opacity: 1, y: 0 }} | |
exit={{ opacity: 0, y: -20 }} | |
className="mt-12 bg-white rounded-xl shadow-xl overflow-hidden" | |
> | |
<div className="relative h-[400px] w-full cursor-pointer" onClick={() => setShowPreview(true)}> | |
{culture.imageUrl && ( | |
<img | |
src={culture.imageUrl} | |
alt={culture.name} | |
className="w-full h-full object-contain" | |
onError={(e) => { | |
console.error('Image failed to load'); | |
e.currentTarget.src = '/placeholder-image.jpg'; | |
}} | |
/> | |
)} | |
<div className="absolute inset-0 bg-black bg-opacity-0 hover:bg-opacity-10 transition-all duration-300 flex items-center justify-center"> | |
<span className="text-white opacity-0 hover:opacity-100 transition-opacity duration-300"> | |
Klik untuk memperbesar | |
</span> | |
</div> | |
</div> | |
<div className="p-6"> | |
<h2 className="text-2xl font-bold mb-4 text-red-800"> | |
{culture.name} | |
</h2> | |
<p className="text-gray-700 leading-relaxed whitespace-pre-wrap"> | |
{culture.description} | |
</p> | |
</div> | |
</motion.div> | |
)} | |
</AnimatePresence> | |
<AnimatePresence> | |
{showPreview && culture && ( | |
<motion.div | |
initial={{ opacity: 0 }} | |
animate={{ opacity: 1 }} | |
exit={{ opacity: 0 }} | |
className="fixed inset-0 bg-black bg-opacity-90 z-50 flex items-center justify-center p-4" | |
onClick={() => setShowPreview(false)} | |
> | |
<motion.div | |
initial={{ scale: 0.9 }} | |
animate={{ scale: 1 }} | |
exit={{ scale: 0.9 }} | |
className="relative max-w-[90vw] max-h-[90vh]" | |
> | |
<img | |
src={culture.imageUrl} | |
alt={culture.name} | |
className="max-w-full max-h-[90vh] object-contain" | |
/> | |
<button | |
className="absolute top-4 right-4 text-white bg-black bg-opacity-50 rounded-full p-2 hover:bg-opacity-70" | |
onClick={() => setShowPreview(false)} | |
> | |
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> | |
</svg> | |
</button> | |
</motion.div> | |
</motion.div> | |
)} | |
</AnimatePresence> | |
</div> | |
</main> | |
); | |
} |