← back to My Childhood Pics

app/page.tsx

588 lines

"use client"

import { useState, useCallback, useRef, useEffect } from "react"
import { Upload, X, Download, ArrowRight, Loader2 } from "lucide-react"
import { motion, AnimatePresence } from "framer-motion"
import { removeBackground } from '@imgly/background-removal'

type UploadSlot = "person1" | "person2" | "environment"

export default function Home() {
  const [person1, setPerson1] = useState<string | null>(null)
  const [person1Processed, setPerson1Processed] = useState<string | null>(null)
  const [person2, setPerson2] = useState<string | null>(null)
  const [person2Processed, setPerson2Processed] = useState<string | null>(null)
  const [environment, setEnvironment] = useState<string | null>(null)
  const [selectedEnv, setSelectedEnv] = useState<string>("")
  const [isGenerating, setIsGenerating] = useState(false)
  const [isProcessing, setIsProcessing] = useState(false)
  const [result, setResult] = useState<string | null>(null)
  const canvasRef = useRef<HTMLCanvasElement>(null)

  // Auto-request location permission on mount
  useEffect(() => {
    if ("geolocation" in navigator) {
      navigator.geolocation.getCurrentPosition(
        (position) => {
          console.log("Location access granted:", position.coords.latitude, position.coords.longitude)
        },
        (error) => {
          console.log("Location access denied or error:", error.message)
        },
        {
          enableHighAccuracy: true,
          timeout: 5000,
          maximumAge: 0
        }
      )
    }
  }, [])

  const environments = [
    { id: "beach", name: "BEACH", img: "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=1200" },
    { id: "mountain", name: "MOUNTAIN", img: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200" },
    { id: "city", name: "CITY", img: "https://images.unsplash.com/photo-1449824913935-59a10b8d2000?w=1200" },
    { id: "forest", name: "FOREST", img: "https://images.unsplash.com/photo-1448375240586-882707db888b?w=1200" },
    { id: "space", name: "SPACE", img: "https://images.unsplash.com/photo-1446776653964-20c1d3a81b06?w=1200" },
    { id: "desert", name: "DESERT", img: "https://images.unsplash.com/photo-1509316785289-025f5b846b35?w=1200" },
  ]

  const handleFileUpload = useCallback(async (files: FileList | null, slot: UploadSlot) => {
    if (files && files[0]) {
      const reader = new FileReader()
      reader.onload = async (e) => {
        const result = e.target?.result as string
        if (slot === "person1") {
          setPerson1(result)
          setIsProcessing(true)
          try {
            // Remove background using AI
            const processedImage = await removeBackground(result)
            const blob = processedImage
            const url = URL.createObjectURL(blob)
            setPerson1Processed(url)
          } catch (error) {
            console.error('Background removal failed:', error)
            setPerson1Processed(result) // Fallback to original
          }
          setIsProcessing(false)
        } else if (slot === "person2") {
          setPerson2(result)
          setIsProcessing(true)
          try {
            // Remove background using AI
            const processedImage = await removeBackground(result)
            const blob = processedImage
            const url = URL.createObjectURL(blob)
            setPerson2Processed(url)
          } catch (error) {
            console.error('Background removal failed:', error)
            setPerson2Processed(result) // Fallback to original
          }
          setIsProcessing(false)
        } else {
          setEnvironment(result)
          setSelectedEnv("")
        }
      }
      reader.readAsDataURL(files[0])
    }
  }, [])

  const extractPerson = (ctx: CanvasRenderingContext2D, img: HTMLImageElement, x: number, y: number, width: number, height: number) => {
    // For now, just composite the person image with blend mode
    // Proper extraction would require ML-based segmentation

    ctx.save()

    // Add shadow for depth
    ctx.shadowColor = 'rgba(0,0,0,0.5)'
    ctx.shadowBlur = 20
    ctx.shadowOffsetX = 0
    ctx.shadowOffsetY = 10

    // Draw the person image
    // Using globalCompositeOperation for better blending
    ctx.globalCompositeOperation = 'source-over'
    ctx.drawImage(img, x, y, width, height)

    ctx.restore()
  }

  const mergeImages = async () => {
    return new Promise<string>((resolve) => {
      const canvas = canvasRef.current
      if (!canvas) return

      const ctx = canvas.getContext('2d')
      if (!ctx) return

      canvas.width = 1200
      canvas.height = 800

      // Get the environment image to use
      let envImage = environment
      if (!envImage && selectedEnv) {
        const selectedEnvData = environments.find(e => e.id === selectedEnv)
        envImage = selectedEnvData?.img || null
      }

      const imagesToLoad: Promise<{type: string, img: HTMLImageElement}>[] = []

      // Load environment first (as background)
      if (envImage) {
        imagesToLoad.push(
          new Promise((resolve) => {
            const img = new Image()
            img.crossOrigin = "anonymous"
            img.onload = () => resolve({type: 'env', img})
            img.src = envImage
          })
        )
      }

      // Load person 1 (use processed version if available)
      if (person1Processed || person1) {
        imagesToLoad.push(
          new Promise((resolve) => {
            const img = new Image()
            img.onload = () => resolve({type: 'person1', img})
            img.src = person1Processed || person1
          })
        )
      }

      // Load person 2 (use processed version if available)
      if (person2Processed || person2) {
        imagesToLoad.push(
          new Promise((resolve) => {
            const img = new Image()
            img.onload = () => resolve({type: 'person2', img})
            img.src = person2Processed || person2
          })
        )
      }

      Promise.all(imagesToLoad).then((loadedImages) => {
        // Clear canvas
        ctx.fillStyle = '#000000'
        ctx.fillRect(0, 0, canvas.width, canvas.height)

        // Find each image type
        const envImg = loadedImages.find(i => i.type === 'env')?.img
        const person1Img = loadedImages.find(i => i.type === 'person1')?.img
        const person2Img = loadedImages.find(i => i.type === 'person2')?.img

        // Draw environment as full background
        if (envImg) {
          ctx.drawImage(envImg, 0, 0, canvas.width, canvas.height)

          // Add subtle vignette effect
          const gradient = ctx.createRadialGradient(
            canvas.width / 2, canvas.height / 2, 0,
            canvas.width / 2, canvas.height / 2, Math.max(canvas.width, canvas.height) / 2
          )
          gradient.addColorStop(0.5, 'rgba(0,0,0,0)')
          gradient.addColorStop(1, 'rgba(0,0,0,0.3)')
          ctx.fillStyle = gradient
          ctx.fillRect(0, 0, canvas.width, canvas.height)
        }

        // Calculate positions and scales for people with better proportions
        const groundLevel = canvas.height * 0.9 // Where feet should be
        const targetPersonHeight = canvas.height * 0.45 // Target height for people in environment

        // Calculate number of people to position
        const peopleCount = (person1Img ? 1 : 0) + (person2Img ? 1 : 0)

        if (peopleCount === 1) {
          // Single person - center them
          const personImg = person1Img || person2Img
          if (personImg) {
            const aspectRatio = personImg.width / personImg.height
            const personHeight = targetPersonHeight
            const personWidth = personHeight * aspectRatio
            const personX = (canvas.width - personWidth) / 2
            const personY = groundLevel - personHeight

            extractPerson(ctx, personImg, personX, personY, personWidth, personHeight)
          }
        } else if (peopleCount === 2) {
          // Two people - position them side by side
          if (person1Img) {
            const aspectRatio = person1Img.width / person1Img.height
            const p1Height = targetPersonHeight
            const p1Width = p1Height * aspectRatio
            const p1X = canvas.width * 0.33 - p1Width / 2
            const p1Y = groundLevel - p1Height

            extractPerson(ctx, person1Img, p1X, p1Y, p1Width, p1Height)
          }

          if (person2Img) {
            const aspectRatio = person2Img.width / person2Img.height
            const p2Height = targetPersonHeight * 0.95 // Slightly smaller for depth
            const p2Width = p2Height * aspectRatio
            const p2X = canvas.width * 0.67 - p2Width / 2
            const p2Y = groundLevel - p2Height

            extractPerson(ctx, person2Img, p2X, p2Y, p2Width, p2Height)
          }
        }

        // Add subtle overlay text at bottom
        const gradient = ctx.createLinearGradient(0, canvas.height - 100, 0, canvas.height)
        gradient.addColorStop(0, 'rgba(0,0,0,0)')
        gradient.addColorStop(1, 'rgba(0,0,0,0.7)')
        ctx.fillStyle = gradient
        ctx.fillRect(0, canvas.height - 100, canvas.width, 100)

        ctx.font = 'bold 20px sans-serif'
        ctx.fillStyle = 'rgba(255,255,255,0.9)'
        ctx.textAlign = 'center'
        ctx.fillText('RECREATED MEMORY', canvas.width / 2, canvas.height - 20)

        // Convert canvas to image
        const mergedImage = canvas.toDataURL('image/png')
        resolve(mergedImage)
      })
    })
  }

  const handleGenerate = async () => {
    if (!person1) return
    if (!selectedEnv && !environment) return

    setIsGenerating(true)

    // Small delay to show loading state
    await new Promise(resolve => setTimeout(resolve, 500))

    // Merge all images
    const mergedImage = await mergeImages()

    // Additional delay for effect
    await new Promise(resolve => setTimeout(resolve, 1000))

    setResult(mergedImage)
    setIsGenerating(false)
  }

  const reset = () => {
    setPerson1(null)
    setPerson1Processed(null)
    setPerson2(null)
    setPerson2Processed(null)
    setEnvironment(null)
    setSelectedEnv("")
    setResult(null)
  }

  const downloadImage = () => {
    if (!result) return
    const link = document.createElement('a')
    link.download = 'recreated-memory.png'
    link.href = result
    link.click()
  }

  const canGenerate = person1 && (selectedEnv || environment)

  return (
    <div className="min-h-screen bg-black text-white p-8">
      <canvas ref={canvasRef} style={{ display: 'none' }} />

      <div className="max-w-6xl mx-auto">
        {/* Header */}
        <motion.div
          initial={{ opacity: 0, y: -20 }}
          animate={{ opacity: 1, y: 0 }}
          className="mb-16"
        >
          <h1 className="text-6xl md:text-8xl font-bold mb-4">RECREATE</h1>
          <p className="text-xl text-white/60">Place your childhood photos in new environments</p>
          <p className="text-sm text-white/40 mt-2">AI automatically removes backgrounds from your photos</p>
        </motion.div>

        {/* Main Grid */}
        <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
          {/* Left: Inputs */}
          <div className="space-y-8">
            {/* Upload Slots */}
            <div className="space-y-4">
              <h2 className="text-sm font-bold tracking-wider text-white/60">PEOPLE TO EXTRACT</h2>

              {/* Person 1 */}
              <motion.div
                initial={{ opacity: 0, x: -20 }}
                animate={{ opacity: 1, x: 0 }}
                transition={{ delay: 0.1 }}
              >
                <div className="border border-white/20 p-4 flex items-center justify-between hover:border-white/40 transition-colors relative">
                  {isProcessing && person1 && !person1Processed && (
                    <div className="absolute inset-0 bg-black/50 flex items-center justify-center z-10">
                      <Loader2 className="w-6 h-6 animate-spin text-white" />
                    </div>
                  )}
                  {person1 ? (
                    <>
                      <div className="flex items-center gap-4">
                        <img src={person1} alt="Person 1" className="w-16 h-16 object-cover" />
                        <span className="text-sm font-medium">PERSON 1 ✓</span>
                      </div>
                      <button
                        onClick={() => {
                          setPerson1(null)
                          setPerson1Processed(null)
                        }}
                        className="p-2 hover:bg-white/10 rounded transition-colors"
                      >
                        <X className="w-4 h-4" />
                      </button>
                    </>
                  ) : (
                    <label className="flex items-center gap-4 cursor-pointer w-full">
                      <input
                        type="file"
                        accept="image/*"
                        onChange={(e) => handleFileUpload(e.target.files, "person1")}
                        className="hidden"
                      />
                      <Upload className="w-4 h-4 text-white/40" />
                      <span className="text-sm text-white/60">PERSON 1 (REQUIRED)</span>
                    </label>
                  )}
                </div>
              </motion.div>

              {/* Person 2 */}
              <motion.div
                initial={{ opacity: 0, x: -20 }}
                animate={{ opacity: 1, x: 0 }}
                transition={{ delay: 0.2 }}
              >
                <div className="border border-white/20 p-4 flex items-center justify-between hover:border-white/40 transition-colors relative">
                  {isProcessing && person2 && !person2Processed && (
                    <div className="absolute inset-0 bg-black/50 flex items-center justify-center z-10">
                      <Loader2 className="w-6 h-6 animate-spin text-white" />
                    </div>
                  )}
                  {person2 ? (
                    <>
                      <div className="flex items-center gap-4">
                        <img src={person2} alt="Person 2" className="w-16 h-16 object-cover" />
                        <span className="text-sm font-medium">PERSON 2 ✓</span>
                      </div>
                      <button
                        onClick={() => {
                          setPerson2(null)
                          setPerson2Processed(null)
                        }}
                        className="p-2 hover:bg-white/10 rounded transition-colors"
                      >
                        <X className="w-4 h-4" />
                      </button>
                    </>
                  ) : (
                    <label className="flex items-center gap-4 cursor-pointer w-full">
                      <input
                        type="file"
                        accept="image/*"
                        onChange={(e) => handleFileUpload(e.target.files, "person2")}
                        className="hidden"
                      />
                      <Upload className="w-4 h-4 text-white/40" />
                      <span className="text-sm text-white/60">PERSON 2 (OPTIONAL)</span>
                    </label>
                  )}
                </div>
              </motion.div>

              {/* Custom Environment */}
              <motion.div
                initial={{ opacity: 0, x: -20 }}
                animate={{ opacity: 1, x: 0 }}
                transition={{ delay: 0.3 }}
              >
                <div className="border border-white/20 p-4 flex items-center justify-between hover:border-white/40 transition-colors">
                  {environment ? (
                    <>
                      <div className="flex items-center gap-4">
                        <img src={environment} alt="Environment" className="w-16 h-16 object-cover" />
                        <span className="text-sm font-medium">CUSTOM ENVIRONMENT ✓</span>
                      </div>
                      <button
                        onClick={() => {
                          setEnvironment(null)
                          setSelectedEnv("")
                        }}
                        className="p-2 hover:bg-white/10 rounded transition-colors"
                      >
                        <X className="w-4 h-4" />
                      </button>
                    </>
                  ) : (
                    <label className="flex items-center gap-4 cursor-pointer w-full">
                      <input
                        type="file"
                        accept="image/*"
                        onChange={(e) => handleFileUpload(e.target.files, "environment")}
                        className="hidden"
                      />
                      <Upload className="w-4 h-4 text-white/40" />
                      <span className="text-sm text-white/60">CUSTOM ENVIRONMENT</span>
                    </label>
                  )}
                </div>
              </motion.div>
            </div>

            {/* Environment Presets */}
            <div className="space-y-4">
              <h2 className="text-sm font-bold tracking-wider text-white/60">SELECT DESTINATION</h2>
              <div className="grid grid-cols-3 gap-2">
                {environments.map((env, i) => (
                  <motion.button
                    key={env.id}
                    initial={{ opacity: 0, scale: 0.9 }}
                    animate={{ opacity: 1, scale: 1 }}
                    transition={{ delay: 0.4 + i * 0.05 }}
                    onClick={() => {
                      setSelectedEnv(env.id)
                      setEnvironment(null)
                    }}
                    className={`relative aspect-video overflow-hidden border ${
                      selectedEnv === env.id
                        ? 'border-white ring-2 ring-white ring-offset-2 ring-offset-black'
                        : 'border-white/20 hover:border-white/40'
                    } transition-all`}
                  >
                    <img src={env.img} alt={env.name} className="w-full h-full object-cover" />
                    <div className="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent" />
                    <span className="absolute bottom-2 left-2 text-xs font-bold">{env.name}</span>
                    {selectedEnv === env.id && (
                      <div className="absolute top-2 right-2 w-4 h-4 bg-white rounded-full flex items-center justify-center">
                        <span className="text-black text-xs">✓</span>
                      </div>
                    )}
                  </motion.button>
                ))}
              </div>
            </div>

            {/* Generate Button */}
            <motion.button
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              transition={{ delay: 0.8 }}
              onClick={handleGenerate}
              disabled={!canGenerate || isGenerating}
              className={`w-full p-4 border flex items-center justify-center gap-3 transition-all ${
                canGenerate && !isGenerating
                  ? 'border-white bg-white text-black hover:bg-white/90 cursor-pointer'
                  : 'border-white/20 text-white/40 cursor-not-allowed'
              }`}
            >
              {isGenerating ? (
                <>
                  <Loader2 className="w-4 h-4 animate-spin" />
                  <span className="font-bold tracking-wider">PROCESSING...</span>
                </>
              ) : (
                <>
                  <span className="font-bold tracking-wider">GENERATE</span>
                  <ArrowRight className="w-4 h-4" />
                </>
              )}
            </motion.button>
          </div>

          {/* Right: Result */}
          <div className="space-y-8">
            <h2 className="text-sm font-bold tracking-wider text-white/60">RECREATED MEMORY</h2>

            <div className="aspect-[3/2] border border-white/20 flex items-center justify-center bg-black/50">
              <AnimatePresence mode="wait">
                {isGenerating ? (
                  <motion.div
                    key="loading"
                    initial={{ opacity: 0 }}
                    animate={{ opacity: 1 }}
                    exit={{ opacity: 0 }}
                    className="text-center"
                  >
                    <Loader2 className="w-12 h-12 animate-spin text-white/40 mx-auto mb-4" />
                    <p className="text-white/40 text-sm">REMOVING BACKGROUNDS...</p>
                    <p className="text-white/20 text-xs mt-2">AI PROCESSING</p>
                  </motion.div>
                ) : result ? (
                  <motion.div
                    key="result"
                    initial={{ scale: 0.8, opacity: 0 }}
                    animate={{ scale: 1, opacity: 1 }}
                    exit={{ scale: 0.8, opacity: 0 }}
                    className="relative w-full h-full"
                  >
                    <img src={result} alt="Recreated Memory" className="w-full h-full object-contain" />
                  </motion.div>
                ) : (
                  <motion.div
                    key="empty"
                    initial={{ opacity: 0 }}
                    animate={{ opacity: 1 }}
                    exit={{ opacity: 0 }}
                    className="text-center"
                  >
                    <p className="text-white/40 text-sm">YOUR RECREATION</p>
                    <p className="text-white/20 text-xs mt-2">WILL APPEAR HERE</p>
                  </motion.div>
                )}
              </AnimatePresence>
            </div>

            {result && !isGenerating && (
              <motion.div
                initial={{ opacity: 0, y: 20 }}
                animate={{ opacity: 1, y: 0 }}
                className="space-y-3"
              >
                <div className="flex gap-3">
                  <button
                    onClick={handleGenerate}
                    className="flex-1 p-3 border border-white/20 hover:border-white/40 transition-colors"
                  >
                    <span className="text-sm">REGENERATE</span>
                  </button>
                  <button
                    onClick={downloadImage}
                    className="flex-1 p-3 border border-white bg-white text-black hover:bg-white/90 flex items-center justify-center gap-2"
                  >
                    <Download className="w-4 h-4" />
                    <span className="text-sm">DOWNLOAD</span>
                  </button>
                </div>

                <button
                  onClick={reset}
                  className="w-full p-3 border border-white/20 hover:border-white/40 transition-colors"
                >
                  <span className="text-sm">START OVER</span>
                </button>
              </motion.div>
            )}
          </div>
        </div>

        {/* Footer */}
        <motion.div
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          transition={{ delay: 1 }}
          className="mt-24 pt-8 border-t border-white/10 text-center"
        >
          <p className="text-xs text-white/40">CHILDHOOD PHOTO RECREATION</p>
        </motion.div>
      </div>
    </div>
  )
}