Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Retro Ghibli Photobooth</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Patrick+Hand&display=swap'); | |
| body { | |
| background-color: #f5e9dc; | |
| font-family: 'Patrick Hand', cursive; | |
| overflow-x: hidden; | |
| } | |
| .ghibli-effect { | |
| filter: sepia(0.3) brightness(1.1) contrast(0.9) saturate(1.2); | |
| position: relative; | |
| } | |
| .ghibli-effect::after { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient(to bottom, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 50%); | |
| mix-blend-mode: overlay; | |
| } | |
| .polaroid-frame { | |
| background: white; | |
| padding: 15px 15px 60px 15px; | |
| box-shadow: 0 4px 8px rgba(0,0,0,0.2), 0 10px 20px rgba(0,0,0,0.15); | |
| transform: rotate(-2deg); | |
| transition: all 0.3s ease; | |
| } | |
| .polaroid-frame:hover { | |
| transform: rotate(1deg) scale(1.02); | |
| } | |
| .polaroid-label { | |
| font-family: 'Fredoka One', cursive; | |
| color: #333; | |
| text-align: center; | |
| margin-top: 10px; | |
| font-size: 1.2rem; | |
| } | |
| .flash { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: white; | |
| opacity: 0; | |
| z-index: 1000; | |
| pointer-events: none; | |
| } | |
| .camera-shutter { | |
| animation: shutter 0.8s cubic-bezier(0.4, 0.0, 0.2, 1); | |
| } | |
| @keyframes shutter { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(0.9); } | |
| 100% { transform: scale(1); } | |
| } | |
| .photo-strip { | |
| background: repeating-linear-gradient( | |
| to bottom, | |
| #f5e9dc, | |
| #f5e9dc 20px, | |
| #d8c9b8 20px, | |
| #d8c9b8 22px | |
| ); | |
| box-shadow: inset 0 0 10px rgba(0,0,0,0.2); | |
| } | |
| .vintage-button { | |
| background: linear-gradient(to bottom, #e74c3c, #c0392b); | |
| color: white; | |
| text-shadow: 0 1px 1px rgba(0,0,0,0.3); | |
| box-shadow: 0 4px 0 #922b21, 0 5px 10px rgba(0,0,0,0.2); | |
| border-radius: 50px; | |
| transition: all 0.2s ease; | |
| } | |
| .vintage-button:active { | |
| transform: translateY(4px); | |
| box-shadow: 0 1px 0 #922b21, 0 2px 5px rgba(0,0,0,0.2); | |
| } | |
| .film-border { | |
| border: 8px solid #1a1a1a; | |
| border-image: repeating-linear-gradient( | |
| to bottom, | |
| #1a1a1a, | |
| #1a1a1a 10px, | |
| #333 10px, | |
| #333 20px | |
| ) 10; | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen py-8"> | |
| <div class="flash" id="flash"></div> | |
| <div class="container mx-auto px-4"> | |
| <h1 class="text-5xl font-bold text-center mb-2 text-amber-900 font-fredoka">Ghibli Photobooth</h1> | |
| <p class="text-xl text-center mb-8 text-amber-800">Capture magical moments with Studio Ghibli style</p> | |
| <div class="flex flex-col lg:flex-row gap-8 items-center justify-center"> | |
| <!-- Camera Section --> | |
| <div class="w-full lg:w-1/2"> | |
| <div class="bg-black p-4 rounded-lg shadow-2xl film-border"> | |
| <div class="relative overflow-hidden rounded-md" id="cameraView"> | |
| <video id="video" class="w-full h-auto" autoplay playsinline></video> | |
| <canvas id="canvas" class="hidden"></canvas> | |
| <div class="absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 text-white p-4 flex justify-center"> | |
| <button id="captureBtn" class="vintage-button px-8 py-4 text-xl flex items-center gap-2"> | |
| <i class="fas fa-camera-retro"></i> Take Photo | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-4 flex gap-4 justify-center"> | |
| <button id="ghibliEffectBtn" class="vintage-button px-6 py-3 flex items-center gap-2"> | |
| <i class="fas fa-magic"></i> Ghibli Effect | |
| </button> | |
| <button id="downloadBtn" class="vintage-button px-6 py-3 flex items-center gap-2" disabled> | |
| <i class="fas fa-download"></i> Download | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Gallery Section --> | |
| <div class="w-full lg:w-1/2"> | |
| <div class="bg-white bg-opacity-70 rounded-xl p-6 shadow-lg"> | |
| <h2 class="text-3xl font-bold mb-4 text-amber-900 font-fredoka">Your Photos</h2> | |
| <div id="photoStrip" class="photo-strip p-4 rounded-lg min-h-64 flex flex-col items-center gap-6"> | |
| <p class="text-gray-600 italic" id="emptyMessage">Your photos will appear here...</p> | |
| <!-- Photos will be added here dynamically --> | |
| </div> | |
| <div class="mt-4 flex justify-between items-center"> | |
| <div class="flex gap-2"> | |
| <button id="clearBtn" class="bg-gray-200 hover:bg-gray-300 px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i class="fas fa-trash"></i> Clear All | |
| </button> | |
| </div> | |
| <div class="text-sm text-gray-600"> | |
| <span id="photoCount">0</span> photos | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Elements | |
| const video = document.getElementById('video'); | |
| const canvas = document.getElementById('canvas'); | |
| const captureBtn = document.getElementById('captureBtn'); | |
| const ghibliEffectBtn = document.getElementById('ghibliEffectBtn'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| const photoStrip = document.getElementById('photoStrip'); | |
| const emptyMessage = document.getElementById('emptyMessage'); | |
| const photoCount = document.getElementById('photoCount'); | |
| const flash = document.getElementById('flash'); | |
| // State | |
| let currentPhoto = null; | |
| let photos = []; | |
| let ghibliEffectEnabled = true; | |
| // Initialize camera | |
| async function initCamera() { | |
| try { | |
| const stream = await navigator.mediaDevices.getUserMedia({ | |
| video: { | |
| width: { ideal: 1280 }, | |
| height: { ideal: 720 }, | |
| facingMode: 'user' | |
| }, | |
| audio: false | |
| }); | |
| video.srcObject = stream; | |
| } catch (err) { | |
| console.error("Error accessing camera:", err); | |
| alert("Could not access the camera. Please make sure you've granted camera permissions."); | |
| } | |
| } | |
| // Capture photo | |
| captureBtn.addEventListener('click', function() { | |
| // Flash effect | |
| flash.style.opacity = 1; | |
| setTimeout(() => { flash.style.opacity = 0; }, 200); | |
| // Camera shutter animation | |
| const cameraView = document.getElementById('cameraView'); | |
| cameraView.classList.add('camera-shutter'); | |
| setTimeout(() => cameraView.classList.remove('camera-shutter'), 800); | |
| // Capture the image | |
| const context = canvas.getContext('2d'); | |
| canvas.width = video.videoWidth; | |
| canvas.height = video.videoHeight; | |
| context.drawImage(video, 0, 0, canvas.width, canvas.height); | |
| // Create photo object | |
| currentPhoto = { | |
| id: Date.now(), | |
| imageData: canvas.toDataURL('image/png'), | |
| hasGhibliEffect: ghibliEffectEnabled | |
| }; | |
| // Add to gallery | |
| addPhotoToGallery(currentPhoto); | |
| downloadBtn.disabled = false; | |
| // Play shutter sound | |
| playShutterSound(); | |
| }); | |
| // Toggle Ghibli effect | |
| ghibliEffectBtn.addEventListener('click', function() { | |
| ghibliEffectEnabled = !ghibliEffectEnabled; | |
| ghibliEffectBtn.innerHTML = ghibliEffectEnabled ? | |
| '<i class="fas fa-magic"></i> Ghibli Effect (ON)' : | |
| '<i class="fas fa-magic"></i> Ghibli Effect (OFF)'; | |
| ghibliEffectBtn.classList.toggle('bg-green-600', ghibliEffectEnabled); | |
| ghibliEffectBtn.classList.toggle('bg-gray-600', !ghibliEffectEnabled); | |
| }); | |
| // Download current photo | |
| downloadBtn.addEventListener('click', function() { | |
| if (!currentPhoto) return; | |
| const link = document.createElement('a'); | |
| link.download = `ghibli-photo-${new Date().toISOString().slice(0,10)}.png`; | |
| link.href = currentPhoto.imageData; | |
| link.click(); | |
| }); | |
| // Clear all photos | |
| clearBtn.addEventListener('click', function() { | |
| if (confirm('Are you sure you want to delete all photos?')) { | |
| photoStrip.innerHTML = '<p class="text-gray-600 italic" id="emptyMessage">Your photos will appear here...</p>'; | |
| photos = []; | |
| currentPhoto = null; | |
| updatePhotoCount(); | |
| downloadBtn.disabled = true; | |
| emptyMessage.style.display = 'block'; | |
| } | |
| }); | |
| // Add photo to gallery | |
| function addPhotoToGallery(photo) { | |
| emptyMessage.style.display = 'none'; | |
| const photoElement = document.createElement('div'); | |
| photoElement.className = 'polaroid-frame w-64'; | |
| photoElement.dataset.id = photo.id; | |
| const img = document.createElement('img'); | |
| img.src = photo.imageData; | |
| img.className = 'w-full h-auto'; | |
| if (photo.hasGhibliEffect) { | |
| img.classList.add('ghibli-effect'); | |
| } | |
| const label = document.createElement('div'); | |
| label.className = 'polaroid-label'; | |
| label.textContent = `Ghibli #${photos.length + 1}`; | |
| photoElement.appendChild(img); | |
| photoElement.appendChild(label); | |
| // Add delete button to each photo | |
| const deleteBtn = document.createElement('button'); | |
| deleteBtn.className = 'absolute top-2 right-2 bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity'; | |
| deleteBtn.innerHTML = '<i class="fas fa-times text-xs"></i>'; | |
| deleteBtn.addEventListener('click', function(e) { | |
| e.stopPropagation(); | |
| if (confirm('Delete this photo?')) { | |
| photoElement.remove(); | |
| photos = photos.filter(p => p.id !== photo.id); | |
| updatePhotoCount(); | |
| if (photos.length === 0) { | |
| emptyMessage.style.display = 'block'; | |
| downloadBtn.disabled = true; | |
| } | |
| } | |
| }); | |
| photoElement.style.position = 'relative'; | |
| photoElement.appendChild(deleteBtn); | |
| // Hover effect to show delete button | |
| photoElement.addEventListener('mouseenter', () => { | |
| deleteBtn.classList.remove('opacity-0'); | |
| deleteBtn.classList.add('opacity-70'); | |
| }); | |
| photoElement.addEventListener('mouseleave', () => { | |
| deleteBtn.classList.remove('opacity-70'); | |
| deleteBtn.classList.add('opacity-0'); | |
| }); | |
| // Click to set as current photo | |
| photoElement.addEventListener('click', function() { | |
| currentPhoto = photos.find(p => p.id === photo.id); | |
| downloadBtn.disabled = false; | |
| }); | |
| photoStrip.insertBefore(photoElement, photoStrip.firstChild); | |
| photos.unshift(photo); | |
| updatePhotoCount(); | |
| } | |
| // Update photo counter | |
| function updatePhotoCount() { | |
| photoCount.textContent = photos.length; | |
| } | |
| // Play shutter sound | |
| function playShutterSound() { | |
| const audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| const oscillator = audioContext.createOscillator(); | |
| const gainNode = audioContext.createGain(); | |
| oscillator.connect(gainNode); | |
| gainNode.connect(audioContext.destination); | |
| oscillator.type = 'sine'; | |
| oscillator.frequency.value = 1000; | |
| gainNode.gain.value = 0.5; | |
| oscillator.start(); | |
| gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3); | |
| oscillator.stop(audioContext.currentTime + 0.3); | |
| } | |
| // Initialize | |
| initCamera(); | |
| ghibliEffectBtn.click(); // Set initial state to ON | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=FGF897/ghibligram" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |