testiris / templates /index.html
LovnishVerma's picture
Update templates/index.html
9c18cbe verified
<!DOCTYPE html>
<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>