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> |