Spaces:
Paused
Paused
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='img/indiaai-logo.jpeg') }}"> | |
| <title>Iris Flower Prediction | AI-Powered Classification</title> | |
| <meta name="description" content="Predict iris flower species using AI. Enter flower measurements for instant classification."> | |
| <style> | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| overflow-x: hidden; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| } | |
| /* Video background with fallback */ | |
| .video-container { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: -2; | |
| overflow: hidden; | |
| } | |
| #backgroundVideo { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| min-width: 100%; | |
| min-height: 100%; | |
| width: auto; | |
| height: auto; | |
| transform: translateX(-50%) translateY(-50%); | |
| object-fit: cover; | |
| } | |
| /* Fallback gradient overlay */ | |
| .video-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.4); | |
| z-index: -1; | |
| } | |
| /* Loading indicator */ | |
| .loading { | |
| display: none; | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| color: white; | |
| font-size: 18px; | |
| z-index: 1000; | |
| } | |
| .spinner { | |
| border: 3px solid rgba(255, 255, 255, 0.3); | |
| border-top: 3px solid white; | |
| border-radius: 50%; | |
| width: 30px; | |
| height: 30px; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto 10px; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| /* Main container */ | |
| .container { | |
| display: flex; | |
| flex-direction: column; | |
| min-height: 100vh; | |
| padding: 20px; | |
| } | |
| /* Header */ | |
| .header { | |
| text-align: center; | |
| color: white; | |
| margin-bottom: 30px; | |
| z-index: 10; | |
| } | |
| .header h1 { | |
| font-size: clamp(2rem, 5vw, 3.5rem); | |
| font-weight: 700; | |
| text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); | |
| margin-bottom: 10px; | |
| } | |
| .header p { | |
| font-size: clamp(1rem, 3vw, 1.2rem); | |
| text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.7); | |
| margin-bottom: 20px; | |
| } | |
| /* Form styling */ | |
| .form-container { | |
| flex: 1; | |
| display: flex; | |
| justify-content: center; | |
| align-items: flex-start; | |
| z-index: 10; | |
| } | |
| .prediction-form { | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(10px); | |
| padding: 30px; | |
| border-radius: 15px; | |
| width: 100%; | |
| max-width: 450px; | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| } | |
| .form-grid { | |
| display: grid; | |
| gap: 20px; | |
| } | |
| .input-group { | |
| position: relative; | |
| } | |
| .input-group label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 600; | |
| color: #333; | |
| font-size: 14px; | |
| } | |
| .input-group input { | |
| width: 100%; | |
| padding: 12px 16px; | |
| border: 2px solid #e1e5e9; | |
| border-radius: 8px; | |
| font-size: 16px; | |
| transition: all 0.3s ease; | |
| background: white; | |
| } | |
| .input-group input:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); | |
| transform: translateY(-1px); | |
| } | |
| .input-group input:invalid { | |
| border-color: #e74c3c; | |
| } | |
| .input-group input:valid { | |
| border-color: #27ae60; | |
| } | |
| /* Button styling */ | |
| .button-group { | |
| display: flex; | |
| gap: 12px; | |
| margin-top: 20px; | |
| } | |
| .btn { | |
| flex: 1; | |
| padding: 14px 20px; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 16px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| text-decoration: none; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| } | |
| .btn-primary:hover:not(:disabled) { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); | |
| } | |
| .btn-secondary { | |
| background: white; | |
| color: #333; | |
| border: 2px solid #e1e5e9; | |
| } | |
| .btn-secondary:hover { | |
| background: #f8f9fa; | |
| border-color: #333; | |
| } | |
| .btn:disabled { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| } | |
| /* Results section */ | |
| .result-container { | |
| margin-top: 30px; | |
| padding: 25px; | |
| background: rgba(255, 255, 255, 0.95); | |
| border-radius: 15px; | |
| text-align: center; | |
| backdrop-filter: blur(10px); | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| } | |
| .result-container h2 { | |
| color: #333; | |
| margin-bottom: 15px; | |
| font-size: 24px; | |
| } | |
| .result-container p { | |
| font-size: 18px; | |
| margin-bottom: 20px; | |
| color: #555; | |
| } | |
| .species-name { | |
| color: #667eea; | |
| font-weight: bold; | |
| text-transform: capitalize; | |
| } | |
| .flower-image { | |
| border-radius: 12px; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | |
| transition: transform 0.3s ease; | |
| max-width: 100%; | |
| height: auto; | |
| } | |
| .flower-image:hover { | |
| transform: scale(1.05); | |
| } | |
| /* Video controls */ | |
| .video-controls { | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| z-index: 100; | |
| } | |
| .video-toggle { | |
| background: rgba(0, 0, 0, 0.7); | |
| color: white; | |
| border: none; | |
| padding: 12px 16px; | |
| border-radius: 25px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| transition: all 0.3s ease; | |
| backdrop-filter: blur(5px); | |
| } | |
| .video-toggle:hover { | |
| background: rgba(0, 0, 0, 0.9); | |
| } | |
| /* Error states */ | |
| .error-message { | |
| color: #e74c3c; | |
| font-size: 14px; | |
| margin-top: 5px; | |
| display: none; | |
| } | |
| /* Responsive design */ | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 15px; | |
| } | |
| .prediction-form { | |
| padding: 20px; | |
| } | |
| .button-group { | |
| flex-direction: column; | |
| } | |
| .video-controls { | |
| bottom: 10px; | |
| right: 10px; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .header h1 { | |
| font-size: 2rem; | |
| } | |
| .prediction-form { | |
| padding: 15px; | |
| } | |
| } | |
| /* Accessibility improvements */ | |
| .sr-only { | |
| position: absolute; | |
| width: 1px; | |
| height: 1px; | |
| padding: 0; | |
| margin: -1px; | |
| overflow: hidden; | |
| clip: rect(0, 0, 0, 0); | |
| white-space: nowrap; | |
| border: 0; | |
| } | |
| /* Focus indicators */ | |
| *:focus-visible { | |
| outline: 2px solid #667eea; | |
| outline-offset: 2px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Loading indicator --> | |
| <div class="loading" id="loading"> | |
| <div class="spinner"></div> | |
| <div>Loading...</div> | |
| </div> | |
| <!-- Background Video --> | |
| <div class="video-container"> | |
| <video | |
| id="backgroundVideo" | |
| autoplay | |
| muted | |
| loop | |
| playsinline | |
| preload="metadata" | |
| aria-label="Background animation" | |
| > | |
| <source src="{{ url_for('static', filename='video/rain.mp4') }}" type="video/mp4"> | |
| <source src="{{ url_for('static', filename='video/rain.webm') }}" type="video/webm"> | |
| </video> | |
| </div> | |
| <!-- Video overlay for better text readability --> | |
| <div class="video-overlay"></div> | |
| <div class="container"> | |
| <!-- Header --> | |
| <header class="header"> | |
| <h1>Iris Flower Classifier</h1> | |
| <p>Enter flower measurements for AI-powered species prediction</p> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="form-container"> | |
| <div class="prediction-form"> | |
| <form id="predictionForm" action="/predict" method="post" novalidate> | |
| <div class="form-grid"> | |
| <div class="input-group"> | |
| <label for="sl">Sepal Length (cm)</label> | |
| <input | |
| type="number" | |
| id="sl" | |
| name="sl" | |
| step="0.1" | |
| min="0" | |
| max="10" | |
| placeholder="e.g. 5.1" | |
| required | |
| aria-describedby="sl-error" | |
| > | |
| <div class="error-message" id="sl-error"></div> | |
| </div> | |
| <div class="input-group"> | |
| <label for="sw">Sepal Width (cm)</label> | |
| <input | |
| type="number" | |
| id="sw" | |
| name="sw" | |
| step="0.1" | |
| min="0" | |
| max="10" | |
| placeholder="e.g. 3.5" | |
| required | |
| aria-describedby="sw-error" | |
| > | |
| <div class="error-message" id="sw-error"></div> | |
| </div> | |
| <div class="input-group"> | |
| <label for="pl">Petal Length (cm)</label> | |
| <input | |
| type="number" | |
| id="pl" | |
| name="pl" | |
| step="0.1" | |
| min="0" | |
| max="10" | |
| placeholder="e.g. 1.4" | |
| required | |
| aria-describedby="pl-error" | |
| > | |
| <div class="error-message" id="pl-error"></div> | |
| </div> | |
| <div class="input-group"> | |
| <label for="pw">Petal Width (cm)</label> | |
| <input | |
| type="number" | |
| id="pw" | |
| name="pw" | |
| step="0.1" | |
| min="0" | |
| max="10" | |
| placeholder="e.g. 0.2" | |
| required | |
| aria-describedby="pw-error" | |
| > | |
| <div class="error-message" id="pw-error"></div> | |
| </div> | |
| </div> | |
| <div class="button-group"> | |
| <button type="submit" class="btn btn-primary" id="submitBtn"> | |
| <span>🔍</span> Predict Species | |
| </button> | |
| <a href="/" class="btn btn-secondary"> | |
| <span>🔄</span> Reset Form | |
| </a> | |
| </div> | |
| </form> | |
| <!-- Prediction Result --> | |
| {% if data %} | |
| <div class="result-container"> | |
| <h2>Prediction Result</h2> | |
| <p>The predicted flower species is: <span class="species-name">{{ data }}</span></p> | |
| <div> | |
| {% if data == "setosa" %} | |
| <img src="https://cdn-uploads.huggingface.co/production/uploads/6474405f90330355db146c76/iVXMOLPdx1ctqWnvnFED_.png" | |
| alt="Iris Setosa flower" | |
| class="flower-image" | |
| width="200" | |
| height="200" | |
| loading="lazy"> | |
| {% elif data == "versicolor" %} | |
| <img src="https://cdn.glitch.global/8599b2d2-58ab-4d42-98f5-97fa4c6bad04/Iris%20versicolor.png?v=1680866854461" | |
| alt="Iris Versicolor flower" | |
| class="flower-image" | |
| width="200" | |
| height="200" | |
| loading="lazy"> | |
| {% elif data == "virginica" %} | |
| <img src="https://cdn.glitch.global/8599b2d2-58ab-4d42-98f5-97fa4c6bad04/Iris%20Virginica.png?v=1680866857022" | |
| alt="Iris Virginica flower" | |
| class="flower-image" | |
| width="200" | |
| height="200" | |
| loading="lazy"> | |
| {% endif %} | |
| </div> | |
| </div> | |
| {% endif %} | |
| </div> | |
| </main> | |
| </div> | |
| <!-- Video Controls --> | |
| <div class="video-controls"> | |
| <button id="videoToggle" class="video-toggle" aria-label="Toggle background video"> | |
| ⏸️ Pause | |
| </button> | |
| </div> | |
| <!-- Screen reader announcements --> | |
| <div aria-live="polite" aria-atomic="true" class="sr-only" id="announcements"></div> | |
| <script> | |
| // DOM elements | |
| const video = document.getElementById("backgroundVideo"); | |
| const videoToggle = document.getElementById("videoToggle"); | |
| const form = document.getElementById("predictionForm"); | |
| const submitBtn = document.getElementById("submitBtn"); | |
| const loading = document.getElementById("loading"); | |
| const announcements = document.getElementById("announcements"); | |
| // Video handling with better error recovery | |
| let videoLoaded = false; | |
| video.addEventListener('loadeddata', () => { | |
| videoLoaded = true; | |
| console.log('Video loaded successfully'); | |
| }); | |
| video.addEventListener('error', (e) => { | |
| console.warn('Video failed to load:', e); | |
| // Hide video controls if video fails | |
| document.querySelector('.video-controls').style.display = 'none'; | |
| }); | |
| // Enhanced video toggle | |
| function toggleVideo() { | |
| if (!videoLoaded) { | |
| announcements.textContent = 'Video not available'; | |
| return; | |
| } | |
| try { | |
| if (video.paused) { | |
| video.play().then(() => { | |
| videoToggle.innerHTML = "⏸️ Pause"; | |
| videoToggle.setAttribute('aria-label', 'Pause background video'); | |
| }).catch(e => { | |
| console.warn('Video play failed:', e); | |
| announcements.textContent = 'Unable to play video'; | |
| }); | |
| } else { | |
| video.pause(); | |
| videoToggle.innerHTML = "▶️ Play"; | |
| videoToggle.setAttribute('aria-label', 'Play background video'); | |
| } | |
| } catch (e) { | |
| console.warn('Video control error:', e); | |
| announcements.textContent = 'Video controls unavailable'; | |
| } | |
| } | |
| videoToggle.addEventListener('click', toggleVideo); | |
| // Form validation | |
| const inputs = form.querySelectorAll('input[required]'); | |
| function validateInput(input) { | |
| const value = parseFloat(input.value); | |
| const errorElement = document.getElementById(input.id + '-error'); | |
| errorElement.style.display = 'none'; | |
| input.classList.remove('error'); | |
| if (!input.value) { | |
| showError(input, errorElement, 'This field is required'); | |
| return false; | |
| } | |
| if (isNaN(value) || value < 0 || value > 10) { | |
| showError(input, errorElement, 'Please enter a valid number between 0 and 10'); | |
| return false; | |
| } | |
| return true; | |
| } | |
| function showError(input, errorElement, message) { | |
| errorElement.textContent = message; | |
| errorElement.style.display = 'block'; | |
| input.classList.add('error'); | |
| } | |
| // Real-time validation | |
| inputs.forEach(input => { | |
| input.addEventListener('blur', () => validateInput(input)); | |
| input.addEventListener('input', () => { | |
| if (input.classList.contains('error')) { | |
| validateInput(input); | |
| } | |
| }); | |
| }); | |
| // Form submission handling | |
| form.addEventListener('submit', (e) => { | |
| let isValid = true; | |
| // Validate all inputs | |
| inputs.forEach(input => { | |
| if (!validateInput(input)) { | |
| isValid = false; | |
| } | |
| }); | |
| if (!isValid) { | |
| e.preventDefault(); | |
| announcements.textContent = 'Please correct the errors in the form'; | |
| return; | |
| } | |
| // Show loading state | |
| submitBtn.disabled = true; | |
| submitBtn.innerHTML = '<div class="spinner"></div> Predicting...'; | |
| loading.style.display = 'block'; | |
| }); | |
| // Auto-focus first input on page load | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const firstInput = document.getElementById('sl'); | |
| if (firstInput) { | |
| firstInput.focus(); | |
| } | |
| }); | |
| // Keyboard navigation improvements | |
| document.addEventListener('keydown', (e) => { | |
| // Escape key to pause/play video | |
| if (e.key === 'Escape' && videoLoaded) { | |
| toggleVideo(); | |
| } | |
| }); | |
| // Handle video autoplay restrictions | |
| document.addEventListener('click', () => { | |
| if (video.paused && videoLoaded) { | |
| video.play().catch(e => { | |
| console.warn('Autoplay blocked:', e); | |
| }); | |
| } | |
| }, { once: true }); | |
| // Intersection Observer for performance | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.style.opacity = '1'; | |
| entry.target.style.transform = 'translateY(0)'; | |
| } | |
| }); | |
| }); | |
| // Observe form elements for smooth entrance | |
| document.querySelectorAll('.input-group').forEach(el => { | |
| el.style.opacity = '0'; | |
| el.style.transform = 'translateY(20px)'; | |
| el.style.transition = 'all 0.6s ease'; | |
| observer.observe(el); | |
| }); | |
| // Service Worker registration for PWA capabilities (optional) | |
| if ('serviceWorker' in navigator) { | |
| window.addEventListener('load', () => { | |
| navigator.serviceWorker.register('/sw.js').catch(err => { | |
| console.log('ServiceWorker registration failed: ', err); | |
| }); | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> |