sampgallery-vbeta1-00 / index.html
privateuserh's picture
Add 3 files
759eb53 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Photo Uploader | Share Your Moments</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.upload-area {
transition: all 0.3s ease;
}
.upload-area.drag-over {
border-color: #4f46e5;
background-color: #f5f3ff;
}
.photo-card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.photo-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.progress-bar {
transition: width 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
.fade-in {
animation: fadeIn 0.3s ease-out forwards;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-12">
<!-- Header -->
<header class="text-center mb-12">
<h1 class="text-4xl font-bold text-indigo-600 mb-2">Photo Vault</h1>
<p class="text-gray-600 max-w-lg mx-auto">Upload and store your favorite moments. Your photos are saved until you delete them.</p>
</header>
<!-- Main Content -->
<main>
<!-- Upload Section -->
<div class="max-w-3xl mx-auto bg-white rounded-xl shadow-md overflow-hidden mb-12">
<div class="p-8">
<div
id="uploadArea"
class="upload-area border-2 border-dashed border-gray-300 rounded-lg p-12 text-center cursor-pointer hover:border-indigo-400"
>
<div class="flex flex-col items-center justify-center">
<i class="fas fa-cloud-upload-alt text-5xl text-indigo-500 mb-4"></i>
<h3 class="text-xl font-semibold text-gray-700 mb-2">Drag & Drop your photos here</h3>
<p class="text-gray-500 mb-6">or click to browse files</p>
<input
type="file"
id="fileInput"
class="hidden"
accept="image/*"
multiple
>
<button
id="selectFilesBtn"
class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-6 rounded-lg transition duration-200"
>
Select Files
</button>
</div>
</div>
<!-- Progress Bar (hidden by default) -->
<div id="progressContainer" class="mt-6 hidden">
<div class="flex justify-between mb-1">
<span class="text-sm font-medium text-gray-700">Uploading...</span>
<span id="progressPercent" class="text-sm font-medium text-gray-700">0%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div id="progressBar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div>
</div>
</div>
</div>
</div>
<!-- Gallery Section -->
<div id="gallerySection" class="max-w-7xl mx-auto">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-semibold text-gray-800">Your Photo Collection</h2>
<div id="photoCount" class="text-sm text-gray-500">0 photos</div>
</div>
<!-- Empty State -->
<div id="emptyState" class="text-center py-12 bg-white rounded-lg shadow-sm">
<i class="fas fa-images text-4xl text-gray-300 mb-4"></i>
<h3 class="text-xl font-medium text-gray-500">Your photo vault is empty</h3>
<p class="text-gray-400 mt-2">Upload your first photo to start your collection</p>
</div>
<!-- Photo Grid -->
<div id="photoGrid" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 hidden">
<!-- Photos will be added here dynamically -->
</div>
</div>
</main>
<!-- Footer -->
<footer class="mt-16 text-center text-gray-500 text-sm">
<p>© 2023 Photo Vault. Your photos are stored locally in your browser.</p>
</footer>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const selectFilesBtn = document.getElementById('selectFilesBtn');
const progressContainer = document.getElementById('progressContainer');
const progressBar = document.getElementById('progressBar');
const progressPercent = document.getElementById('progressPercent');
const photoGrid = document.getElementById('photoGrid');
const emptyState = document.getElementById('emptyState');
const gallerySection = document.getElementById('gallerySection');
const photoCount = document.getElementById('photoCount');
// Load saved photos from localStorage
loadSavedPhotos();
// Click event for the upload button
selectFilesBtn.addEventListener('click', () => fileInput.click());
// Handle file selection
fileInput.addEventListener('change', handleFiles);
// Drag and drop events
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
uploadArea.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
uploadArea.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
uploadArea.addEventListener(eventName, unhighlight, false);
});
function highlight() {
uploadArea.classList.add('drag-over');
}
function unhighlight() {
uploadArea.classList.remove('drag-over');
}
// Handle dropped files
uploadArea.addEventListener('drop', function(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles({ target: { files } });
});
// Process selected files
function handleFiles(e) {
const files = e.target.files;
if (!files.length) return;
// Show progress bar
progressContainer.classList.remove('hidden');
// Simulate upload progress
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 10;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
// Hide progress bar after a delay
setTimeout(() => {
progressContainer.classList.add('hidden');
}, 1000);
}
progressBar.style.width = `${progress}%`;
progressPercent.textContent = `${Math.round(progress)}%`;
}, 200);
// Process each file
Array.from(files).forEach(file => {
if (!file.type.match('image.*')) return;
const reader = new FileReader();
reader.onload = function(e) {
// Create photo object
const photo = {
id: Date.now().toString(36) + Math.random().toString(36).substr(2),
name: file.name,
size: file.size,
dataUrl: e.target.result,
uploadedAt: new Date().toISOString()
};
// Save to localStorage
savePhoto(photo);
// Display the photo
displayPhoto(photo);
// Update photo count
updatePhotoCount();
};
reader.readAsDataURL(file);
});
}
// Load saved photos from localStorage
function loadSavedPhotos() {
const savedPhotos = getSavedPhotos();
if (savedPhotos.length > 0) {
emptyState.classList.add('hidden');
photoGrid.classList.remove('hidden');
savedPhotos.forEach(photo => {
displayPhoto(photo);
});
updatePhotoCount();
}
}
// Get saved photos from localStorage
function getSavedPhotos() {
const savedPhotos = localStorage.getItem('photoVault');
return savedPhotos ? JSON.parse(savedPhotos) : [];
}
// Save photo to localStorage
function savePhoto(photo) {
const savedPhotos = getSavedPhotos();
savedPhotos.unshift(photo); // Add new photo at beginning
localStorage.setItem('photoVault', JSON.stringify(savedPhotos));
}
// Remove photo from localStorage
function removePhoto(id) {
const savedPhotos = getSavedPhotos();
const updatedPhotos = savedPhotos.filter(photo => photo.id !== id);
localStorage.setItem('photoVault', JSON.stringify(updatedPhotos));
updatePhotoCount();
}
// Display photo in the grid
function displayPhoto(photo) {
// Hide empty state and show grid if first image
if (photoGrid.children.length === 0) {
emptyState.classList.add('hidden');
photoGrid.classList.remove('hidden');
}
// Create photo card
const photoCard = document.createElement('div');
photoCard.className = 'photo-card bg-white rounded-lg overflow-hidden shadow-md hover:shadow-lg fade-in';
photoCard.dataset.id = photo.id;
photoCard.innerHTML = `
<div class="relative pb-[100%]">
<img src="${photo.dataUrl}" alt="${photo.name}" class="absolute h-full w-full object-cover">
</div>
<div class="p-4">
<div class="flex justify-between items-center">
<h3 class="font-medium text-gray-800 truncate">${photo.name}</h3>
<span class="text-xs text-gray-500">${formatFileSize(photo.size)}</span>
</div>
<div class="flex justify-between mt-3">
<button class="text-indigo-600 hover:text-indigo-800 text-sm font-medium download-btn">
<i class="fas fa-download mr-1"></i> Download
</button>
<button class="text-red-500 hover:text-red-700 text-sm font-medium delete-btn">
<i class="fas fa-trash mr-1"></i> Delete
</button>
</div>
</div>
`;
// Add delete functionality
photoCard.querySelector('.delete-btn').addEventListener('click', function() {
photoCard.classList.add('opacity-0', 'scale-95');
setTimeout(() => {
// Remove from DOM
photoGrid.removeChild(photoCard);
// Remove from localStorage
removePhoto(photo.id);
// Show empty state if no photos left
if (photoGrid.children.length === 0) {
emptyState.classList.remove('hidden');
photoGrid.classList.add('hidden');
}
}, 300);
});
// Add download functionality
photoCard.querySelector('.download-btn').addEventListener('click', function() {
downloadPhoto(photo.dataUrl, photo.name);
});
// Add to grid
photoGrid.prepend(photoCard);
}
// Download photo
function downloadPhoto(dataUrl, filename) {
const link = document.createElement('a');
link.href = dataUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Update photo count display
function updatePhotoCount() {
const count = getSavedPhotos().length;
photoCount.textContent = `${count} ${count === 1 ? 'photo' : 'photos'}`;
}
// Helper function to format file size
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
});
</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=privateuserh/sampgallery-vbeta1-00" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>