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> | |
| ); | |
| } |