ghibligram / index.html
FGF897's picture
Add 2 files
9a0df66 verified
<!DOCTYPE html>
<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>