Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Image Rating System</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| .rating-line { | |
| background: linear-gradient(to right, red, yellow, green); | |
| } | |
| .draggable-image { | |
| transition: transform 0.2s; | |
| } | |
| .draggable-image.dragging { | |
| opacity: 0.5; | |
| transform: scale(1.1); | |
| } | |
| .drop-zone { | |
| transition: background-color 0.2s; | |
| } | |
| .drop-zone.highlight { | |
| background-color: rgba(0, 0, 0, 0.1); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <div class="max-w-4xl mx-auto bg-white rounded-xl shadow-md overflow-hidden p-6"> | |
| <h1 class="text-3xl font-bold text-center text-indigo-700 mb-6">Rate These Images</h1> | |
| <div class="mb-8"> | |
| <label for="username" class="block text-sm font-medium text-gray-700 mb-2">Your Username</label> | |
| <input type="text" id="username" | |
| class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" | |
| placeholder="Enter your username"> | |
| </div> | |
| <div class="mb-6"> | |
| <p class="text-sm text-gray-600 mb-2">Drag and drop images on the line below from <span class="font-bold text-red-500">worst</span> to <span class="font-bold text-green-600">best</span></p> | |
| <div class="rating-line h-4 rounded-full mb-4 relative"> | |
| <div class="absolute left-0 -bottom-6 text-xs text-red-500">Worst</div> | |
| <div class="absolute right-0 -bottom-6 text-xs text-green-600">Best</div> | |
| </div> | |
| <div id="image-container" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-4 mb-8"> | |
| <!-- Images will be loaded here --> | |
| </div> | |
| <div id="rating-line-container" class="relative h-32 bg-gray-100 rounded-lg drop-zone p-4"> | |
| <div class="absolute inset-0 flex items-center justify-center text-gray-400" id="empty-message"> | |
| Drag images here to rate them | |
| </div> | |
| <div id="rating-area" class="relative h-full flex items-center"></div> | |
| </div> | |
| </div> | |
| <div class="flex justify-center"> | |
| <button id="submit-btn" | |
| class="px-6 py-3 bg-indigo-600 text-white font-medium rounded-md hover:bg-indigo-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" | |
| disabled> | |
| Submit Ratings | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Image data | |
| const images = [ | |
| { id: 1, url: "https://www.vectorstock.com/royalty-free-vector/square-shapes-in-different-colors-vector-29224168" }, | |
| { id: 2, url: "https://www.freepik.com/premium-vector/colorful-square-with-squares-different-colors_40716687.htm" }, | |
| { id: 3, url: "https://www.freepik.com/premium-ai-image/colorful-square-with-many-squares-different-colors_61409150.htm" }, | |
| { id: 4, url: "https://www.pinterest.com/pin/242631498655376169/" }, | |
| { id: 5, url: "https://www.vectorstock.com/royalty-free-vector/colourful-squares-vector-2709299" }, | |
| { id: 6, url: "https://pixabay.com/en/color-square-arrangement-tile-198892/" }, | |
| { id: 7, url: "https://www.dreamstime.com/stock-illustration-set-geometric-shapes-made-up-squares-different-colors-image52041527" }, | |
| { id: 8, url: "https://www.dreamstime.com/royalty-free-stock-photography-color-squares-two-different-sizes-shadow-beige-background-image37420697" }, | |
| { id: 9, url: "https://www.teachercreated.com/products/spot-on-carpet-markers-colorful-squares-4-77049" }, | |
| { id: 10, url: "https://www.vectorstock.com/royalty-free-vector/background-different-colors-separated-squares-vector-32141203" } | |
| ]; | |
| const imageContainer = document.getElementById('image-container'); | |
| const ratingArea = document.getElementById('rating-area'); | |
| const emptyMessage = document.getElementById('empty-message'); | |
| const submitBtn = document.getElementById('submit-btn'); | |
| const usernameInput = document.getElementById('username'); | |
| const ratingLineContainer = document.getElementById('rating-line-container'); | |
| // Load images into the container | |
| images.forEach(image => { | |
| const imgElement = document.createElement('div'); | |
| imgElement.className = 'draggable-image cursor-move bg-white p-2 rounded-lg shadow-md hover:shadow-lg transition-shadow'; | |
| imgElement.draggable = true; | |
| imgElement.dataset.id = image.id; | |
| imgElement.innerHTML = ` | |
| <img src="${image.url}" alt="Image ${image.id}" class="w-full h-24 object-cover rounded"> | |
| <div class="text-center mt-1 text-xs text-gray-600">Image ${image.id}</div> | |
| `; | |
| imgElement.addEventListener('dragstart', handleDragStart); | |
| imgElement.addEventListener('dragend', handleDragEnd); | |
| imageContainer.appendChild(imgElement); | |
| }); | |
| // Rating line event listeners | |
| ratingLineContainer.addEventListener('dragover', handleDragOver); | |
| ratingLineContainer.addEventListener('dragleave', handleDragLeave); | |
| ratingLineContainer.addEventListener('drop', handleDrop); | |
| // Username input validation | |
| usernameInput.addEventListener('input', validateForm); | |
| // Submit button handler | |
| submitBtn.addEventListener('click', submitRatings); | |
| let draggedItem = null; | |
| let isFromRatingArea = false; | |
| function handleDragStart(e) { | |
| draggedItem = e.target.closest('.draggable-image'); | |
| isFromRatingArea = draggedItem.parentElement === ratingArea; | |
| e.dataTransfer.setData('text/plain', draggedItem.dataset.id); | |
| setTimeout(() => { | |
| draggedItem.classList.add('dragging', 'opacity-50', 'scale-110'); | |
| }, 0); | |
| } | |
| function handleDragEnd() { | |
| draggedItem.classList.remove('dragging', 'opacity-50', 'scale-110'); | |
| draggedItem = null; | |
| isFromRatingArea = false; | |
| } | |
| function handleDragOver(e) { | |
| e.preventDefault(); | |
| ratingLineContainer.classList.add('highlight'); | |
| } | |
| function handleDragLeave() { | |
| ratingLineContainer.classList.remove('highlight'); | |
| } | |
| function handleDrop(e) { | |
| e.preventDefault(); | |
| ratingLineContainer.classList.remove('highlight'); | |
| const id = e.dataTransfer.getData('text/plain'); | |
| const droppedImage = document.querySelector(`.draggable-image[data-id="${id}"]`); | |
| // If the image is already in the rating area, just update its position | |
| if (isFromRatingArea) { | |
| updateImagePosition(droppedImage, e.clientX); | |
| return; | |
| } | |
| // Check if this image is already in the rating area | |
| const existingImage = ratingArea.querySelector(`.draggable-image[data-id="${id}"]`); | |
| if (existingImage) { | |
| updateImagePosition(existingImage, e.clientX); | |
| return; | |
| } | |
| // Remove from original container | |
| droppedImage.remove(); | |
| // Add to rating area | |
| positionNewImage(droppedImage, e.clientX); | |
| ratingArea.appendChild(droppedImage); | |
| // Update empty message | |
| if (ratingArea.children.length > 0) { | |
| emptyMessage.style.display = 'none'; | |
| } | |
| validateForm(); | |
| } | |
| function positionNewImage(imageElement, clientX) { | |
| const rect = ratingArea.getBoundingClientRect(); | |
| const xPos = clientX - rect.left; | |
| const percentage = Math.min(100, Math.max(0, (xPos / rect.width) * 100)); | |
| imageElement.style.position = 'absolute'; | |
| imageElement.style.left = `${percentage}%`; | |
| imageElement.style.transform = 'translateX(-50%)'; | |
| // Set initial rating based on position | |
| const rating = Math.round((percentage / 100) * 9) + 1; // 1-10 scale | |
| imageElement.dataset.rating = rating; | |
| // Add rating indicator | |
| const ratingIndicator = document.createElement('div'); | |
| ratingIndicator.className = 'absolute -bottom-6 left-1/2 transform -translate-x-1/2 text-xs font-bold'; | |
| ratingIndicator.textContent = rating; | |
| ratingIndicator.style.color = getRatingColor(rating); | |
| imageElement.appendChild(ratingIndicator); | |
| } | |
| function updateImagePosition(imageElement, clientX) { | |
| const rect = ratingArea.getBoundingClientRect(); | |
| const xPos = clientX - rect.left; | |
| const percentage = Math.min(100, Math.max(0, (xPos / rect.width) * 100)); | |
| imageElement.style.left = `${percentage}%`; | |
| // Update rating based on new position | |
| const rating = Math.round((percentage / 100) * 9) + 1; // 1-10 scale | |
| imageElement.dataset.rating = rating; | |
| // Update rating indicator | |
| const ratingIndicator = imageElement.querySelector('div:last-child'); | |
| ratingIndicator.textContent = rating; | |
| ratingIndicator.style.color = getRatingColor(rating); | |
| } | |
| function getRatingColor(rating) { | |
| // Red (1) to yellow (5) to green (10) | |
| if (rating <= 3) return '#ef4444'; // red-500 | |
| if (rating <= 6) return '#eab308'; // yellow-500 | |
| return '#22c55e'; // green-500 | |
| } | |
| function validateForm() { | |
| const hasRatings = ratingArea.children.length > 0; | |
| const hasUsername = usernameInput.value.trim().length > 0; | |
| submitBtn.disabled = !(hasRatings && hasUsername); | |
| } | |
| async function submitRatings() { | |
| const username = usernameInput.value.trim(); | |
| const ratings = []; | |
| Array.from(ratingArea.children).forEach(item => { | |
| ratings.push({ | |
| imageId: parseInt(item.dataset.id), | |
| rating: parseInt(item.dataset.rating) | |
| }); | |
| }); | |
| // Prepare data for API | |
| const data = { | |
| username, | |
| ratings | |
| }; | |
| // In a real app, you would send this to your API | |
| console.log('Submitting data:', data); | |
| // Simulate API call | |
| submitBtn.disabled = true; | |
| submitBtn.textContent = 'Submitting...'; | |
| try { | |
| // Replace with actual API call | |
| // const response = await fetch('your-api-endpoint', { | |
| // method: 'POST', | |
| // headers: { | |
| // 'Content-Type': 'application/json', | |
| // }, | |
| // body: JSON.stringify(data) | |
| // }); | |
| // Simulate API delay | |
| await new Promise(resolve => setTimeout(resolve, 1500)); | |
| // Show success message | |
| alert('Your ratings have been submitted successfully!'); | |
| // Reset form | |
| usernameInput.value = ''; | |
| ratingArea.innerHTML = ''; | |
| emptyMessage.style.display = 'block'; | |
| submitBtn.disabled = true; | |
| submitBtn.textContent = 'Submit Ratings'; | |
| // Reload images | |
| imageContainer.innerHTML = ''; | |
| images.forEach(image => { | |
| const imgElement = document.createElement('div'); | |
| imgElement.className = 'draggable-image cursor-move bg-white p-2 rounded-lg shadow-md hover:shadow-lg transition-shadow'; | |
| imgElement.draggable = true; | |
| imgElement.dataset.id = image.id; | |
| imgElement.innerHTML = ` | |
| <img src="${image.url}" alt="Image ${image.id}" class="w-full h-24 object-cover rounded"> | |
| <div class="text-center mt-1 text-xs text-gray-600">Image ${image.id}</div> | |
| `; | |
| imgElement.addEventListener('dragstart', handleDragStart); | |
| imgElement.addEventListener('dragend', handleDragEnd); | |
| imageContainer.appendChild(imgElement); | |
| }); | |
| } catch (error) { | |
| console.error('Error submitting ratings:', error); | |
| alert('There was an error submitting your ratings. Please try again.'); | |
| submitBtn.disabled = false; | |
| submitBtn.textContent = 'Submit Ratings'; | |
| } | |
| } | |
| }); | |
| </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=dlindh/image-rater" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |