Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Image Flipper App</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
.image-container { | |
transition: transform 0.3s ease; | |
} | |
.flip-btn:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
} | |
.file-input-label { | |
cursor: pointer; | |
transition: all 0.3s ease; | |
} | |
.file-input-label:hover { | |
background-color: #f3f4f6; | |
} | |
.flip-animation { | |
animation: flip 0.5s ease; | |
} | |
@keyframes flip { | |
0% { transform: scaleX(1); } | |
50% { transform: scaleX(0); } | |
100% { transform: scaleX(1); } | |
} | |
</style> | |
</head> | |
<body class="bg-gray-100 min-h-screen flex flex-col items-center py-12"> | |
<div class="w-full max-w-4xl bg-white rounded-xl shadow-lg overflow-hidden"> | |
<!-- Header --> | |
<div class="bg-indigo-600 py-6 px-8 text-white"> | |
<h1 class="text-3xl font-bold">Image Flipper</h1> | |
<p class="mt-2 opacity-90">Upload an image and flip it horizontally or vertically</p> | |
</div> | |
<!-- Main Content --> | |
<div class="p-8"> | |
<!-- File Upload Section --> | |
<div class="mb-8"> | |
<label class="file-input-label flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-lg p-12 text-center cursor-pointer hover:border-indigo-400 transition-colors duration-300" id="drop-area"> | |
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-indigo-500 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> | |
</svg> | |
<span class="text-xl font-medium text-gray-700">Drag & drop your image here</span> | |
<span class="text-gray-500 mt-2">or click to browse files</span> | |
<input type="file" id="file-input" accept="image/*" class="hidden"> | |
</label> | |
</div> | |
<!-- Image Display Section --> | |
<div class="flex flex-col items-center"> | |
<div class="relative mb-8 w-full max-w-md" id="image-wrapper"> | |
<div class="image-container bg-gray-100 rounded-lg overflow-hidden shadow-md" id="image-container"> | |
<img id="preview-image" class="w-full h-auto object-contain max-h-96" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%239C92AC'%3E%3Cpath d='M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z'/%3E%3C/svg%3E" alt="Preview will appear here"> | |
</div> | |
<div class="absolute -bottom-5 left-0 right-0 flex justify-center space-x-4"> | |
<button id="reset-btn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded-full transition-all duration-200 opacity-0 pointer-events-none"> | |
Reset | |
</button> | |
</div> | |
</div> | |
<!-- Flip Controls --> | |
<div class="flex flex-wrap justify-center gap-4 mb-8" id="flip-controls"> | |
<button id="flip-horizontal" class="flip-btn bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-3 px-6 rounded-lg flex items-center transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" /> | |
</svg> | |
Flip Horizontal | |
</button> | |
<button id="flip-vertical" class="flip-btn bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-3 px-6 rounded-lg flex items-center transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 17V7m0 10l-4 4m4-4l4 4M8 7v10m0-10l4-4m-4 4l-4 4" /> | |
</svg> | |
Flip Vertical | |
</button> | |
</div> | |
<!-- Download Button --> | |
<div class="w-full flex justify-center"> | |
<button id="download-btn" class="bg-green-600 hover:bg-green-700 text-white font-medium py-3 px-6 rounded-lg flex items-center transition-all duration-200 opacity-0 pointer-events-none"> | |
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /> | |
</svg> | |
Download Flipped Image | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
const fileInput = document.getElementById('file-input'); | |
const dropArea = document.getElementById('drop-area'); | |
const previewImage = document.getElementById('preview-image'); | |
const flipHorizontalBtn = document.getElementById('flip-horizontal'); | |
const flipVerticalBtn = document.getElementById('flip-vertical'); | |
const resetBtn = document.getElementById('reset-btn'); | |
const downloadBtn = document.getElementById('download-btn'); | |
const imageContainer = document.getElementById('image-container'); | |
let originalImageSrc = null; | |
let currentImageSrc = null; | |
let canvas = null; | |
// Handle file selection | |
fileInput.addEventListener('change', function(e) { | |
if (e.target.files.length) { | |
const file = e.target.files[0]; | |
processImage(file); | |
} | |
}); | |
// Handle drag and drop | |
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
dropArea.addEventListener(eventName, preventDefaults, false); | |
}); | |
function preventDefaults(e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
} | |
['dragenter', 'dragover'].forEach(eventName => { | |
dropArea.addEventListener(eventName, highlight, false); | |
}); | |
['dragleave', 'drop'].forEach(eventName => { | |
dropArea.addEventListener(eventName, unhighlight, false); | |
}); | |
function highlight() { | |
dropArea.classList.add('border-indigo-500', 'bg-indigo-50'); | |
} | |
function unhighlight() { | |
dropArea.classList.remove('border-indigo-500', 'bg-indigo-50'); | |
} | |
dropArea.addEventListener('drop', function(e) { | |
const dt = e.dataTransfer; | |
const file = dt.files[0]; | |
if (file && file.type.match('image.*')) { | |
processImage(file); | |
} | |
}); | |
// Process the uploaded image | |
function processImage(file) { | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
originalImageSrc = e.target.result; | |
currentImageSrc = originalImageSrc; | |
previewImage.src = originalImageSrc; | |
// Enable buttons | |
flipHorizontalBtn.disabled = false; | |
flipVerticalBtn.disabled = false; | |
// Show reset button | |
resetBtn.classList.remove('opacity-0', 'pointer-events-none'); | |
resetBtn.classList.add('opacity-100', 'pointer-events-auto'); | |
// Hide download button until flip | |
downloadBtn.classList.add('opacity-0', 'pointer-events-none'); | |
downloadBtn.classList.remove('opacity-100', 'pointer-events-auto'); | |
// Create canvas for manipulation | |
createCanvas(originalImageSrc); | |
}; | |
reader.readAsDataURL(file); | |
} | |
// Create canvas for image manipulation | |
function createCanvas(imageSrc) { | |
const img = new Image(); | |
img.onload = function() { | |
if (canvas) { | |
document.body.removeChild(canvas); | |
} | |
canvas = document.createElement('canvas'); | |
canvas.style.display = 'none'; | |
document.body.appendChild(canvas); | |
canvas.width = img.width; | |
canvas.height = img.height; | |
const ctx = canvas.getContext('2d'); | |
ctx.drawImage(img, 0, 0); | |
}; | |
img.src = imageSrc; | |
} | |
// Flip image horizontally | |
flipHorizontalBtn.addEventListener('click', function() { | |
flipImage('horizontal'); | |
}); | |
// Flip image vertically | |
flipVerticalBtn.addEventListener('click', function() { | |
flipImage('vertical'); | |
}); | |
// Reset image to original | |
resetBtn.addEventListener('click', function() { | |
previewImage.src = originalImageSrc; | |
currentImageSrc = originalImageSrc; | |
createCanvas(originalImageSrc); | |
// Hide download button | |
downloadBtn.classList.add('opacity-0', 'pointer-events-none'); | |
downloadBtn.classList.remove('opacity-100', 'pointer-events-auto'); | |
// Add animation | |
imageContainer.classList.add('flip-animation'); | |
setTimeout(() => { | |
imageContainer.classList.remove('flip-animation'); | |
}, 500); | |
}); | |
// Flip the image | |
function flipImage(direction) { | |
if (!canvas) return; | |
const img = new Image(); | |
img.onload = function() { | |
const ctx = canvas.getContext('2d'); | |
// Clear canvas | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
// Save the current context | |
ctx.save(); | |
if (direction === 'horizontal') { | |
// Flip horizontally | |
ctx.translate(canvas.width, 0); | |
ctx.scale(-1, 1); | |
} else { | |
// Flip vertically | |
ctx.translate(0, canvas.height); | |
ctx.scale(1, -1); | |
} | |
// Draw the image | |
ctx.drawImage(img, 0, 0); | |
// Restore the context | |
ctx.restore(); | |
// Update the preview | |
currentImageSrc = canvas.toDataURL('image/png'); | |
previewImage.src = currentImageSrc; | |
// Show download button | |
downloadBtn.classList.remove('opacity-0', 'pointer-events-none'); | |
downloadBtn.classList.add('opacity-100', 'pointer-events-auto'); | |
// Add animation | |
imageContainer.classList.add('flip-animation'); | |
setTimeout(() => { | |
imageContainer.classList.remove('flip-animation'); | |
}, 500); | |
}; | |
img.src = currentImageSrc; | |
} | |
// Download the flipped image | |
downloadBtn.addEventListener('click', function() { | |
if (!currentImageSrc) return; | |
const link = document.createElement('a'); | |
link.download = 'flipped-image.png'; | |
link.href = currentImageSrc; | |
document.body.appendChild(link); | |
link.click(); | |
document.body.removeChild(link); | |
}); | |
}); | |
</script> | |
</html> |