pranit144's picture
Update templates/index.html
7418d85 verified
<!DOCTYPE html>
<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 !important;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;
}
.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>