← back to My Childhood Pics
components/ui/spacex-photo-recreator.tsx
430 lines
"use client"
import type React from "react"
import { useState, useCallback } from "react"
import Image from "next/image"
import {
Sparkles,
Upload,
User,
Users,
MapPin,
Download,
ArrowRight,
X,
} from "lucide-react"
import { motion } from "framer-motion"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
type UploadMode = "person1" | "person2" | "environment"
interface GenerationSettings {
person1Image: string | null
person2Image: string | null
environmentImage: string | null
selectedEnvironment: string
}
interface SuggestedEnvironment {
id: string
name: string
image: string
}
const suggestedEnvironments: SuggestedEnvironment[] = [
{
id: "mars",
name: "MARS SURFACE",
image: "https://images.unsplash.com/photo-1614728894747-a83421e2b9c9?w=400",
},
{
id: "moon",
name: "LUNAR BASE",
image: "https://images.unsplash.com/photo-1446941103752-6d3a49ebe962?w=400",
},
{
id: "station",
name: "SPACE STATION",
image: "https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a?w=400",
},
{
id: "earth",
name: "EARTH ORBIT",
image: "https://images.unsplash.com/photo-1611918126831-0a8352d6196f?w=400",
},
{
id: "nebula",
name: "NEBULA",
image: "https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=400",
},
{
id: "galaxy",
name: "GALAXY",
image: "https://images.unsplash.com/photo-1543722530-d2c3201371e7?w=400",
},
]
export function SpaceXPhotoRecreator() {
const [mode, setMode] = useState<UploadMode>("person1")
const [isGenerating, setIsGenerating] = useState(false)
const [generatedImage, setGeneratedImage] = useState<string | null>(null)
const [showResult, setShowResult] = useState(false)
const [settings, setSettings] = useState<GenerationSettings>({
person1Image: null,
person2Image: null,
environmentImage: null,
selectedEnvironment: "",
})
const handleFileUpload = useCallback((files: FileList | null, uploadMode: UploadMode) => {
if (files && files[0]) {
const file = files[0]
const reader = new FileReader()
reader.onload = (e) => {
const result = e.target?.result as string
if (uploadMode === "person1") {
setSettings(prev => ({ ...prev, person1Image: result }))
} else if (uploadMode === "person2") {
setSettings(prev => ({ ...prev, person2Image: result }))
} else {
setSettings(prev => ({ ...prev, environmentImage: result, selectedEnvironment: "custom" }))
}
}
reader.readAsDataURL(file)
}
}, [])
const handleGenerate = async () => {
if (!settings.person1Image) return
if (!settings.selectedEnvironment && !settings.environmentImage) return
setIsGenerating(true)
// Simulate generation
await new Promise(resolve => setTimeout(resolve, 3000))
setGeneratedImage("https://images.unsplash.com/photo-1516849677043-ef67c9557e16?w=800")
setShowResult(true)
setIsGenerating(false)
}
const clearImage = (type: 'person1' | 'person2' | 'environment') => {
if (type === 'person1') {
setSettings(prev => ({ ...prev, person1Image: null }))
} else if (type === 'person2') {
setSettings(prev => ({ ...prev, person2Image: null }))
} else {
setSettings(prev => ({ ...prev, environmentImage: null, selectedEnvironment: '' }))
}
}
const isReady = settings.person1Image && (settings.selectedEnvironment || settings.environmentImage)
return (
<div className="w-full">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
{/* Upload Section */}
<div className="space-y-8">
<div>
<h3 className="text-xs font-bold uppercase tracking-[0.3em] text-white/60 mb-6">
Upload Photos
</h3>
{/* Upload Cards */}
<div className="space-y-4">
{/* Person 1 */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.1 }}
className={cn(
"relative border border-white/10 bg-black/40 backdrop-blur-sm p-6 hover:border-white/30 transition-all cursor-pointer",
settings.person1Image && "border-white/30 bg-white/5"
)}
>
<input
type="file"
accept="image/*"
onChange={(e) => handleFileUpload(e.target.files, "person1")}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10"
/>
<div className="flex items-center justify-between relative z-0">
<div className="flex items-center gap-4">
{settings.person1Image ? (
<div className="w-16 h-16 relative">
<Image
src={settings.person1Image}
alt="Person 1"
fill
className="object-cover"
/>
</div>
) : (
<div className="w-16 h-16 border border-white/20 flex items-center justify-center">
<User className="w-6 h-6 text-white/40" />
</div>
)}
<div>
<div className="text-sm font-medium text-white">PERSON 1</div>
<div className="text-xs text-white/40 mt-1">
{settings.person1Image ? "Image uploaded" : "Required"}
</div>
</div>
</div>
{settings.person1Image && (
<button
onClick={(e) => {
e.stopPropagation()
clearImage('person1')
}}
className="relative z-20 p-2 hover:bg-white/10 transition-colors"
>
<X className="w-4 h-4 text-white/60" />
</button>
)}
</div>
</motion.div>
{/* Person 2 */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.2 }}
className={cn(
"relative border border-white/10 bg-black/40 backdrop-blur-sm p-6 hover:border-white/30 transition-all cursor-pointer",
settings.person2Image && "border-white/30 bg-white/5"
)}
>
<input
type="file"
accept="image/*"
onChange={(e) => handleFileUpload(e.target.files, "person2")}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10"
/>
<div className="flex items-center justify-between relative z-0">
<div className="flex items-center gap-4">
{settings.person2Image ? (
<div className="w-16 h-16 relative">
<Image
src={settings.person2Image}
alt="Person 2"
fill
className="object-cover"
/>
</div>
) : (
<div className="w-16 h-16 border border-white/20 flex items-center justify-center">
<Users className="w-6 h-6 text-white/40" />
</div>
)}
<div>
<div className="text-sm font-medium text-white">PERSON 2</div>
<div className="text-xs text-white/40 mt-1">Optional</div>
</div>
</div>
{settings.person2Image && (
<button
onClick={(e) => {
e.stopPropagation()
clearImage('person2')
}}
className="relative z-20 p-2 hover:bg-white/10 transition-colors"
>
<X className="w-4 h-4 text-white/60" />
</button>
)}
</div>
</motion.div>
{/* Custom Environment */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.3 }}
className={cn(
"relative border border-white/10 bg-black/40 backdrop-blur-sm p-6 hover:border-white/30 transition-all cursor-pointer",
settings.environmentImage && "border-white/30 bg-white/5"
)}
>
<input
type="file"
accept="image/*"
onChange={(e) => handleFileUpload(e.target.files, "environment")}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10"
/>
<div className="flex items-center justify-between relative z-0">
<div className="flex items-center gap-4">
{settings.environmentImage ? (
<div className="w-16 h-16 relative">
<Image
src={settings.environmentImage}
alt="Environment"
fill
className="object-cover"
/>
</div>
) : (
<div className="w-16 h-16 border border-white/20 flex items-center justify-center">
<MapPin className="w-6 h-6 text-white/40" />
</div>
)}
<div>
<div className="text-sm font-medium text-white">CUSTOM ENVIRONMENT</div>
<div className="text-xs text-white/40 mt-1">Upload your own</div>
</div>
</div>
{settings.environmentImage && (
<button
onClick={(e) => {
e.stopPropagation()
clearImage('environment')
}}
className="relative z-20 p-2 hover:bg-white/10 transition-colors"
>
<X className="w-4 h-4 text-white/60" />
</button>
)}
</div>
</motion.div>
</div>
</div>
{/* Environment Selection */}
<div>
<h3 className="text-xs font-bold uppercase tracking-[0.3em] text-white/60 mb-6">
Or Select Environment
</h3>
<div className="grid grid-cols-3 gap-2">
{suggestedEnvironments.map((env, index) => (
<motion.button
key={env.id}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: index * 0.05 }}
onClick={() => setSettings(prev => ({
...prev,
selectedEnvironment: env.id,
environmentImage: null
}))}
className={cn(
"relative aspect-video overflow-hidden border transition-all",
settings.selectedEnvironment === env.id
? "border-white scale-95"
: "border-white/10 hover:border-white/30"
)}
>
<Image
src={env.image}
alt={env.name}
fill
className="object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent" />
<div className="absolute bottom-2 left-2 right-2">
<p className="text-[10px] font-bold uppercase tracking-wider text-white">
{env.name}
</p>
</div>
</motion.button>
))}
</div>
</div>
{/* Generate Button */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
>
<Button
onClick={handleGenerate}
disabled={!isReady || isGenerating}
className={cn(
"w-full h-14 text-xs font-bold uppercase tracking-[0.3em] border transition-all",
isReady
? "bg-white text-black hover:bg-white/90 border-white"
: "bg-transparent text-white/40 border-white/20 cursor-not-allowed"
)}
>
{isGenerating ? (
<span className="flex items-center gap-3">
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
>
<Sparkles className="w-4 h-4" />
</motion.div>
Processing...
</span>
) : (
<span className="flex items-center gap-3">
Generate
<ArrowRight className="w-4 h-4" />
</span>
)}
</Button>
</motion.div>
</div>
{/* Result Section */}
<div className="relative">
<h3 className="text-xs font-bold uppercase tracking-[0.3em] text-white/60 mb-6">
Recreation Result
</h3>
{showResult && generatedImage ? (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
className="space-y-6"
>
<div className="relative aspect-square overflow-hidden border border-white/10">
<Image
src={generatedImage}
alt="Generated result"
fill
className="object-cover"
/>
</div>
<div className="flex gap-3">
<Button
variant="outline"
className="flex-1 h-12 text-xs font-bold uppercase tracking-wider border-white/20 text-white hover:bg-white/10"
onClick={handleGenerate}
>
Regenerate
</Button>
<Button
className="flex-1 h-12 text-xs font-bold uppercase tracking-wider bg-white text-black hover:bg-white/90"
>
<Download className="w-4 h-4 mr-2" />
Download
</Button>
</div>
</motion.div>
) : (
<div className="aspect-square border border-white/10 bg-black/40 backdrop-blur-sm flex items-center justify-center">
<div className="text-center">
<div className="w-20 h-20 border border-white/20 rounded-full flex items-center justify-center mx-auto mb-4">
<Sparkles className="w-8 h-8 text-white/20" />
</div>
<p className="text-xs font-medium uppercase tracking-wider text-white/40">
Your recreation will appear here
</p>
</div>
</div>
)}
</div>
</div>
</div>
)
}