Spaces:
Sleeping
Sleeping
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Crop Swapping Strategy Optimizer</title> | |
<!-- Tailwind CSS CDN --> | |
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet" /> | |
<!-- Add Font Awesome for better icons --> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" /> | |
<!-- Add Animation library --> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> | |
<!-- Google Fonts for better Malayalam support --> | |
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Malayalam:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
<!-- Custom CSS for Enhanced UI --> | |
<style> | |
/* Custom font support for Malayalam and other languages */ | |
body { | |
font-family: 'Inter', 'Noto Sans Malayalam', sans-serif; | |
background: linear-gradient(120deg, #e0f2f1, #f0fff4, #e8f5e9); | |
background-size: 400% 400%; | |
animation: gradientBG 15s ease infinite; | |
line-height: 1.6; | |
} | |
@keyframes gradientBG { | |
0% { background-position: 0% 50%; } | |
50% { background-position: 100% 50%; } | |
100% { background-position: 0% 50%; } | |
} | |
/* Card hover effects - Fixed z-index issues */ | |
.card-hover { | |
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
position: relative; | |
z-index: 1; | |
} | |
.card-hover:hover { | |
transform: translateY(-3px); | |
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15); | |
z-index: 2; | |
} | |
/* Button animations - Fixed ripple effect */ | |
.btn-animated { | |
position: relative; | |
overflow: hidden; | |
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
border: none; | |
outline: none; | |
} | |
.btn-animated:after { | |
content: ''; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
width: 0; | |
height: 0; | |
background: rgba(255, 255, 255, 0.3); | |
border-radius: 50%; | |
transform: translate(-50%, -50%); | |
transition: width 0.6s, height 0.6s; | |
} | |
.btn-animated:active:after { | |
width: 300px; | |
height: 300px; | |
} | |
/* Input focus animations - Fixed focus states */ | |
.input-focus { | |
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
border: 2px solid #e2e8f0; | |
background-color: #ffffff; | |
} | |
.input-focus:focus { | |
border-color: #4CAF50; | |
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1); | |
background-color: #fefefe; | |
outline: none; | |
} | |
.input-focus:hover { | |
border-color: #cbd5e0; | |
} | |
/* Table improvements - Fixed responsive issues */ | |
.table-responsive { | |
overflow-x: auto; | |
-webkit-overflow-scrolling: touch; | |
} | |
.table-row { | |
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); | |
} | |
.table-row:hover { | |
background-color: #f0fff4; | |
transform: translateX(2px); | |
} | |
/* Fixed output styling */ | |
.output-container { | |
max-width: 100%; | |
overflow-x: auto; | |
background: #ffffff; | |
border-radius: 8px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
padding: 0; | |
} | |
.plain-output { | |
font-size: 1rem; | |
line-height: 1.6; | |
color: #333; | |
padding: 1.5rem; | |
word-wrap: break-word; | |
overflow-wrap: break-word; | |
} | |
/* Fixed scrollbar */ | |
::-webkit-scrollbar { | |
width: 8px; | |
height: 8px; | |
} | |
::-webkit-scrollbar-track { | |
background: #f7fafc; | |
border-radius: 4px; | |
} | |
::-webkit-scrollbar-thumb { | |
background: linear-gradient(45deg, #4CAF50, #66BB6A); | |
border-radius: 4px; | |
} | |
::-webkit-scrollbar-thumb:hover { | |
background: linear-gradient(45deg, #388E3C, #4CAF50); | |
} | |
/* Remove button improvements */ | |
.remove-btn { | |
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); | |
padding: 8px; | |
border-radius: 4px; | |
opacity: 0.7; | |
background-color: transparent; | |
} | |
.remove-btn:hover { | |
opacity: 1; | |
background-color: #fee2e2; | |
color: #dc2626; | |
transform: scale(1.05); | |
} | |
/* Page title animation */ | |
.title-animate { | |
animation: slideInDown 0.8s cubic-bezier(0.4, 0, 0.2, 1); | |
} | |
@keyframes slideInDown { | |
from { | |
opacity: 0; | |
transform: translate3d(0, -30px, 0); | |
} | |
to { | |
opacity: 1; | |
transform: translate3d(0, 0, 0); | |
} | |
} | |
/* Error message styling */ | |
.error-message { | |
background-color: #fee2e2; | |
border: 1px solid #fca5a5; | |
color: #dc2626; | |
padding: 1rem; | |
border-radius: 8px; | |
margin: 1rem 0; | |
} | |
/* Loading spinner */ | |
.loading { | |
display: none; | |
text-align: center; | |
padding: 2rem; | |
} | |
.spinner { | |
border: 3px solid #f3f3f3; | |
border-top: 3px solid #4CAF50; | |
border-radius: 50%; | |
width: 30px; | |
height: 30px; | |
animation: spin 1s linear infinite; | |
margin: 0 auto 1rem; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
/* Mobile responsiveness improvements */ | |
@media (max-width: 768px) { | |
.container-mobile { | |
padding: 1rem; | |
} | |
.grid-mobile { | |
grid-template-columns: 1fr; | |
gap: 1rem; | |
} | |
.text-mobile { | |
font-size: 1.5rem; | |
} | |
.table-mobile { | |
font-size: 0.875rem; | |
} | |
} | |
/* Form validation styles */ | |
.input-error { | |
border-color: #ef4444 ; | |
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) ; | |
} | |
.error-text { | |
color: #ef4444; | |
font-size: 0.875rem; | |
margin-top: 0.25rem; | |
} | |
</style> | |
</head> | |
<body class="min-h-screen flex flex-col items-center justify-center p-6"> | |
<div class="w-full max-w-6xl animate__animated animate__fadeIn container-mobile"> | |
<h1 class="text-4xl md:text-5xl font-bold text-center text-green-700 mb-8 title-animate text-mobile"> | |
<i class="fas fa-seedling mr-2"></i>Crop Swapping Strategy Optimizer | |
</h1> | |
<form id="cropForm" action="/" method="POST" class="bg-white shadow-lg rounded-lg px-6 md:px-8 pt-6 pb-8 mb-6 card-hover"> | |
<!-- Farm Information --> | |
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6 grid-mobile"> | |
<div> | |
<label class="block text-green-700 text-sm font-bold mb-2" for="location"> | |
<i class="fas fa-map-marker-alt mr-1"></i> Location | |
</label> | |
<input class="shadow appearance-none border rounded w-full py-3 px-4 text-green-700 leading-tight focus:outline-none focus:shadow-outline input-focus" | |
id="location" name="location" type="text" placeholder="Enter location" required /> | |
<div class="error-text" id="location-error"></div> | |
</div> | |
<div> | |
<label class="block text-green-700 text-sm font-bold mb-2" for="season"> | |
<i class="fas fa-cloud-sun mr-1"></i> Season | |
</label> | |
<select id="season" name="season" class="shadow appearance-none border rounded w-full py-3 px-4 text-green-700 leading-tight focus:outline-none focus:shadow-outline input-focus" required> | |
<option value="" disabled selected>Select Season</option> | |
<option value="Rabi">Rabi</option> | |
<option value="Kharif">Kharif</option> | |
<option value="Zaid">Zaid</option> | |
</select> | |
<div class="error-text" id="season-error"></div> | |
</div> | |
<div> | |
<label class="block text-green-700 text-sm font-bold mb-2" for="total_area"> | |
<i class="fas fa-ruler-combined mr-1"></i> Total Farm Area (acres) | |
</label> | |
<input class="shadow appearance-none border rounded w-full py-3 px-4 text-green-700 leading-tight focus:outline-none focus:shadow-outline input-focus" | |
id="total_area" name="total_area" type="number" step="0.1" min="0.1" placeholder="Enter total farm area" required /> | |
<div class="error-text" id="total_area-error"></div> | |
</div> | |
<div> | |
<label class="block text-green-700 text-sm font-bold mb-2" for="language"> | |
<i class="fas fa-language mr-1"></i> Language | |
</label> | |
<select id="language" name="language" class="shadow appearance-none border rounded w-full py-3 px-4 text-green-700 leading-tight focus:outline-none focus:shadow-outline input-focus"> | |
<option value="English">English</option> | |
<option value="Malayalam">Malayalam</option> | |
<option value="Marathi">Marathi</option> | |
<option value="Hindi">Hindi</option> | |
<option value="Urdu">Urdu</option> | |
<option value="Bengali">Bengali</option> | |
<option value="Odia">Odia</option> | |
<option value="Telugu">Telugu</option> | |
<option value="Tamil">Tamil</option> | |
</select> | |
</div> | |
</div> | |
<!-- Dynamic Crop Details Table --> | |
<h2 class="text-2xl font-semibold text-green-600 mt-8 mb-4 text-center animate__animated animate__fadeIn"> | |
<i class="fas fa-leaf mr-2"></i>Crop Details | |
</h2> | |
<div class="table-responsive mb-6 rounded-lg shadow"> | |
<table class="min-w-full divide-y divide-green-200 table-mobile" id="cropTable"> | |
<thead class="bg-green-100"> | |
<tr> | |
<th class="px-4 md:px-6 py-3 text-left text-xs font-medium text-green-700 uppercase tracking-wider">Crop Name</th> | |
<th class="px-4 md:px-6 py-3 text-left text-xs font-medium text-green-700 uppercase tracking-wider">Area (acres)</th> | |
<th class="px-4 md:px-6 py-3 text-center text-xs font-medium text-green-700 uppercase tracking-wider">Action</th> | |
</tr> | |
</thead> | |
<tbody class="bg-white divide-y divide-green-200" id="cropTableBody"> | |
<!-- Initial Crop Row --> | |
<tr class="table-row animate__animated animate__fadeIn"> | |
<td class="px-4 md:px-6 py-4 whitespace-nowrap"> | |
<input name="crop_name" type="text" placeholder="e.g., Tomato" required | |
class="shadow appearance-none border rounded w-full py-2 px-3 text-green-700 input-focus crop-name-input" /> | |
</td> | |
<td class="px-4 md:px-6 py-4 whitespace-nowrap"> | |
<input name="crop_area" type="number" step="0.1" min="0.1" placeholder="e.g., 2.5" required | |
class="shadow appearance-none border rounded w-full py-2 px-3 text-green-700 input-focus crop-area-input" /> | |
</td> | |
<td class="px-4 md:px-6 py-4 whitespace-nowrap text-center"> | |
<button type="button" class="text-red-500 hover:text-red-700 remove-btn" onclick="removeRow(this)"> | |
<i class="fas fa-trash-alt"></i> | |
</button> | |
</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
<div class="mb-6 flex justify-center"> | |
<button type="button" onclick="addRow()" id="addRowBtn" | |
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded btn-animated"> | |
<i class="fas fa-plus mr-2"></i>Add Crop | |
</button> | |
</div> | |
<div class="flex items-center justify-center"> | |
<button class="bg-green-500 hover:bg-green-700 text-white font-bold py-3 px-6 rounded-lg focus:outline-none focus:shadow-outline btn-animated" type="submit" id="submitBtn"> | |
<i class="fas fa-magic mr-2"></i>Optimize Strategy | |
</button> | |
</div> | |
</form> | |
<!-- Loading spinner --> | |
<div class="loading" id="loadingSpinner"> | |
<div class="spinner"></div> | |
<p class="text-green-700 font-medium">Generating your crop strategy...</p> | |
</div> | |
<!-- Display error if any --> | |
{% if error %} | |
<div class="error-message animate__animated animate__shakeX"> | |
<i class="fas fa-exclamation-triangle mr-2"></i>{{ error }} | |
</div> | |
{% endif %} | |
<!-- Render the optimized output if available --> | |
{% if result %} | |
<div class="bg-white shadow-lg rounded-lg px-6 md:px-8 pt-6 pb-8 animate__animated animate__fadeInUp card-hover output-container"> | |
<h2 class="text-2xl font-bold text-center text-green-700 mb-4"> | |
<i class="fas fa-chart-line mr-2"></i>Optimized Crop Swapping Strategies | |
</h2> | |
<div class="plain-output"> | |
{{ result|safe }} | |
</div> | |
</div> | |
{% endif %} | |
<footer class="text-center text-green-700 mt-6 opacity-75 animate__animated animate__fadeIn"> | |
<p>© 2025 Crop Swapping Strategy Optimizer</p> | |
</footer> | |
</div> | |
<script> | |
let rowCount = 1; | |
// Function to validate form | |
function validateForm() { | |
let isValid = true; | |
const errors = {}; | |
// Clear previous errors | |
document.querySelectorAll('.error-text').forEach(el => el.textContent = ''); | |
document.querySelectorAll('.input-error').forEach(el => el.classList.remove('input-error')); | |
// Validate location | |
const location = document.getElementById('location').value.trim(); | |
if (!location) { | |
errors.location = 'Location is required'; | |
isValid = false; | |
} | |
// Validate season | |
const season = document.getElementById('season').value; | |
if (!season) { | |
errors.season = 'Season is required'; | |
isValid = false; | |
} | |
// Validate total area | |
const totalArea = parseFloat(document.getElementById('total_area').value); | |
if (!totalArea || totalArea <= 0) { | |
errors.total_area = 'Total area must be greater than 0'; | |
isValid = false; | |
} | |
// Validate crop details | |
const cropNames = document.querySelectorAll('.crop-name-input'); | |
const cropAreas = document.querySelectorAll('.crop-area-input'); | |
let totalCropArea = 0; | |
let hasCrops = false; | |
for (let i = 0; i < cropNames.length; i++) { | |
const name = cropNames[i].value.trim(); | |
const area = parseFloat(cropAreas[i].value); | |
if (name && area) { | |
hasCrops = true; | |
totalCropArea += area; | |
} | |
} | |
if (!hasCrops) { | |
alert('Please add at least one crop with name and area'); | |
isValid = false; | |
} | |
if (hasCrops && totalArea && totalCropArea > totalArea) { | |
errors.total_area = `Total crop area (${totalCropArea}) cannot exceed farm area (${totalArea})`; | |
isValid = false; | |
} | |
// Display errors | |
Object.keys(errors).forEach(field => { | |
const errorElement = document.getElementById(field + '-error'); | |
const inputElement = document.getElementById(field); | |
if (errorElement) { | |
errorElement.textContent = errors[field]; | |
} | |
if (inputElement) { | |
inputElement.classList.add('input-error'); | |
} | |
}); | |
return isValid; | |
} | |
// Function to add a new crop row with animation | |
function addRow() { | |
rowCount++; | |
const tableBody = document.getElementById("cropTableBody"); | |
const newRow = document.createElement("tr"); | |
newRow.className = "table-row animate__animated animate__fadeIn"; | |
newRow.innerHTML = ` | |
<td class="px-4 md:px-6 py-4 whitespace-nowrap"> | |
<input name="crop_name" type="text" placeholder="e.g., Rice" required | |
class="shadow appearance-none border rounded w-full py-2 px-3 text-green-700 input-focus crop-name-input" /> | |
</td> | |
<td class="px-4 md:px-6 py-4 whitespace-nowrap"> | |
<input name="crop_area" type="number" step="0.1" min="0.1" placeholder="e.g., 1.5" required | |
class="shadow appearance-none border rounded w-full py-2 px-3 text-green-700 input-focus crop-area-input" /> | |
</td> | |
<td class="px-4 md:px-6 py-4 whitespace-nowrap text-center"> | |
<button type="button" class="text-red-500 hover:text-red-700 remove-btn" onclick="removeRow(this)"> | |
<i class="fas fa-trash-alt"></i> | |
</button> | |
</td> | |
`; | |
tableBody.appendChild(newRow); | |
} | |
// Function to remove a crop row with animation | |
function removeRow(button) { | |
const tableBody = document.getElementById("cropTableBody"); | |
const rows = tableBody.querySelectorAll('tr'); | |
// Prevent removing the last row | |
if (rows.length <= 1) { | |
alert('At least one crop row is required'); | |
return; | |
} | |
const row = button.closest("tr"); | |
row.classList.add("animate__animated", "animate__fadeOut"); | |
// Wait for animation to complete before removing the row | |
setTimeout(() => { | |
row.remove(); | |
rowCount--; | |
}, 500); | |
} | |
// Add form submission handling | |
document.getElementById("cropForm").addEventListener("submit", function(e) { | |
e.preventDefault(); | |
if (!validateForm()) { | |
return false; | |
} | |
// Show loading spinner | |
document.getElementById('loadingSpinner').style.display = 'block'; | |
document.getElementById('submitBtn').disabled = true; | |
document.getElementById('submitBtn').innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Processing...'; | |
// Submit form | |
this.submit(); | |
}); | |
// Real-time validation for total area vs crop areas | |
function validateAreas() { | |
const totalArea = parseFloat(document.getElementById('total_area').value) || 0; | |
const cropAreas = document.querySelectorAll('.crop-area-input'); | |
let totalCropArea = 0; | |
cropAreas.forEach(input => { | |
const area = parseFloat(input.value) || 0; | |
totalCropArea += area; | |
}); | |
const errorElement = document.getElementById('total_area-error'); | |
const totalAreaInput = document.getElementById('total_area'); | |
if (totalArea > 0 && totalCropArea > totalArea) { | |
errorElement.textContent = `Crop areas total (${totalCropArea.toFixed(1)}) exceeds farm area (${totalArea})`; | |
totalAreaInput.classList.add('input-error'); | |
} else { | |
errorElement.textContent = ''; | |
totalAreaInput.classList.remove('input-error'); | |
} | |
} | |
// Add event listeners for real-time validation | |
document.getElementById('total_area').addEventListener('input', validateAreas); | |
document.addEventListener('input', function(e) { | |
if (e.target.classList.contains('crop-area-input')) { | |
validateAreas(); | |
} | |
}); | |
// Animate input fields on focus | |
document.querySelectorAll("input, select").forEach(element => { | |
element.addEventListener("focus", function() { | |
this.classList.add("animate__animated", "animate__pulse"); | |
setTimeout(() => { | |
this.classList.remove("animate__animated", "animate__pulse"); | |
}, 1000); | |
}); | |
}); | |
// Add keyboard shortcuts | |
document.addEventListener('keydown', function(e) { | |
// Alt + A to add row | |
if (e.altKey && e.key === 'a') { | |
e.preventDefault(); | |
addRow(); | |
} | |
// Ctrl + Enter to submit form | |
if (e.ctrlKey && e.key === 'Enter') { | |
e.preventDefault(); | |
document.getElementById('cropForm').dispatchEvent(new Event('submit')); | |
} | |
}); | |
// Initialize tooltips and accessibility features | |
document.addEventListener('DOMContentLoaded', function() { | |
// Add ARIA labels | |
document.querySelectorAll('input').forEach(input => { | |
const label = input.closest('div').querySelector('label'); | |
if (label) { | |
input.setAttribute('aria-label', label.textContent.trim()); | |
} | |
}); | |
// Focus first input | |
document.getElementById('location').focus(); | |
}); | |
</script> | |
</body> | |
</html> |