Spaces:
Sleeping
Sleeping
<!-- templates/index.html --> | |
<html lang="{{ current_language }}"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Agricultural Pests & Diseases in India</title> | |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | |
<style> | |
:root { | |
--primary-color: #38b000; | |
--secondary-color: #007bff; | |
--accent-color: #fb8500; | |
--light-bg: #f8f9fa; | |
--dark-text: #212529; | |
--pest-color: #dc3545; | |
--disease-color: #6f42c1; | |
} | |
body { | |
background-color: var(--light-bg); | |
padding: 20px; | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
} | |
.page-header { | |
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
color: white; | |
padding: 2rem 0; | |
border-radius: 10px; | |
margin-bottom: 2rem; | |
box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |
} | |
.language-selector select { | |
background-color: rgba(255, 255, 255, 0.2); | |
color: white; | |
border: 1px solid rgba(255, 255, 255, 0.3); | |
} | |
.language-selector select option { | |
background-color: white; | |
color: var(--dark-text); | |
} | |
.filter-buttons { | |
margin-bottom: 20px; | |
} | |
.filter-btn { | |
margin-right: 10px; | |
border-radius: 20px; | |
font-weight: 500; | |
padding: 8px 16px; | |
transition: all 0.3s ease; | |
} | |
.filter-btn:hover { | |
transform: translateY(-2px); | |
} | |
.filter-btn.active { | |
box-shadow: 0 4px 8px rgba(0,0,0,0.15); | |
} | |
.card { | |
transition: all 0.3s ease; | |
height: 100%; | |
cursor: pointer; | |
border-radius: 10px; | |
border: none; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.05); | |
overflow: hidden; | |
} | |
.card:hover { | |
transform: translateY(-8px); | |
box-shadow: 0 12px 24px rgba(0,0,0,0.15); | |
} | |
.card-img-top { | |
height: 200px; | |
object-fit: cover; | |
transition: all 0.5s ease; | |
} | |
.card:hover .card-img-top { | |
transform: scale(1.05); | |
} | |
.card-body { | |
padding: 1.5rem; | |
} | |
.card-title { | |
font-weight: 600; | |
margin-bottom: 10px; | |
} | |
.loading { | |
display: none; | |
text-align: center; | |
padding: 20px; | |
} | |
.modal-body { | |
max-height: 70vh; | |
overflow-y: auto; | |
} | |
.badge.pest { | |
background-color: var(--pest-color); | |
padding: 0.5em 0.8em; | |
font-size: 0.8rem; | |
} | |
.badge.disease { | |
background-color: var(--disease-color); | |
padding: 0.5em 0.8em; | |
font-size: 0.8rem; | |
} | |
.modal-content { | |
border-radius: 15px; | |
border: none; | |
box-shadow: 0 10px 30px rgba(0,0,0,0.2); | |
} | |
.modal-header { | |
border-bottom: 1px solid rgba(0,0,0,0.1); | |
background-color: var(--light-bg); | |
} | |
.modal-title { | |
font-weight: 600; | |
} | |
.section-title { | |
margin-top: 1.5rem; | |
margin-bottom: 0.75rem; | |
font-weight: 600; | |
color: var(--dark-text); | |
border-bottom: 2px solid var(--primary-color); | |
padding-bottom: 0.5rem; | |
display: inline-block; | |
} | |
#backToTop { | |
position: fixed; | |
bottom: 20px; | |
right: 20px; | |
display: none; | |
background-color: var(--primary-color); | |
color: white; | |
border: none; | |
border-radius: 50%; | |
width: 50px; | |
height: 50px; | |
text-align: center; | |
font-size: 20px; | |
line-height: 50px; | |
cursor: pointer; | |
z-index: 99; | |
box-shadow: 0 4px 10px rgba(0,0,0,0.2); | |
} | |
.crop-badge { | |
background-color: #17a2b8; | |
color: white; | |
font-size: 0.8rem; | |
font-weight: normal; | |
margin-right: 4px; | |
} | |
.no-results { | |
text-align: center; | |
padding: 40px; | |
font-size: 1.2rem; | |
color: #6c757d; | |
} | |
/* Spinner */ | |
.spinner { | |
width: 40px; | |
height: 40px; | |
margin: 20px auto; | |
border: 4px solid rgba(0, 0, 0, 0.1); | |
border-left-color: var(--primary-color); | |
border-radius: 50%; | |
animation: spin 1s linear infinite; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<header class="page-header text-center mb-4"> | |
<h1 class="display-4">Agricultural Pests & Diseases in India</h1> | |
<p class="lead">Explore common agricultural threats and learn how to manage them</p> | |
<!-- Language selector --> | |
<div class="language-selector mt-3"> | |
<form id="languageForm" class="d-flex justify-content-center align-items-center"> | |
<label for="languageSelect" class="me-2"> | |
<i class="fas fa-language"></i> Language: | |
</label> | |
<select id="languageSelect" class="form-select form-select-sm" style="max-width: 200px;"> | |
{% for code, name in languages.items() %} | |
<option value="{{ code }}" {% if code == current_language %}selected{% endif %}>{{ name }}</option> | |
{% endfor %} | |
</select> | |
</form> | |
</div> | |
</header> | |
<!-- Filter buttons --> | |
<div class="filter-buttons text-center mb-4"> | |
<button class="btn btn-outline-primary filter-btn active" data-filter="all">All Items</button> | |
<button class="btn btn-outline-danger filter-btn" data-filter="pest">Pests Only</button> | |
<button class="btn btn-outline-purple filter-btn" data-filter="disease" style="border-color: #6f42c1; color: #6f42c1;">Diseases Only</button> | |
<div class="input-group mt-3 w-50 mx-auto"> | |
<span class="input-group-text"><i class="fas fa-search"></i></span> | |
<input type="text" class="form-control" id="searchInput" placeholder="Search by name or crop..."> | |
</div> | |
</div> | |
<div class="row row-cols-1 row-cols-md-3 g-4" id="itemsContainer"> | |
{% for item in pests_diseases %} | |
<div class="col item-card" data-type="{{ item.type }}" data-name="{{ item.name.lower() }}" data-crop="{{ item.crop.lower() }}"> | |
<div class="card h-100" onclick="showDetails({{ item.id }})"> | |
<div class="position-relative overflow-hidden"> | |
<img data-src="{{ item.image_url }}" class="card-img-top lazy-load" alt="{{ item.name }}"> | |
<span class="position-absolute top-0 end-0 m-2 badge {{ item.type }}"> | |
{{ item.type|capitalize }} | |
</span> | |
</div> | |
<div class="card-body"> | |
<h5 class="card-title">{{ item.name }}</h5> | |
<p class="card-text"> | |
<strong>Affects:</strong><br> | |
{% for crop in item.crop.split(', ') %} | |
<span class="badge crop-badge">{{ crop }}</span> | |
{% endfor %} | |
</p> | |
</div> | |
</div> | |
</div> | |
{% endfor %} | |
</div> | |
<div id="noResults" class="no-results" style="display: none;"> | |
<i class="fas fa-search fa-3x mb-3 text-muted"></i> | |
<p>No matching items found. Try another search term.</p> | |
</div> | |
<!-- Loading indicator --> | |
<div id="loading" class="loading mt-4"> | |
<div class="spinner"></div> | |
<p class="mt-2">Fetching detailed information...</p> | |
</div> | |
<!-- Detail Modal --> | |
<div class="modal fade" id="detailModal" tabindex="-1" aria-hidden="true"> | |
<div class="modal-dialog modal-lg"> | |
<div class="modal-content"> | |
<div class="modal-header"> | |
<h5 class="modal-title" id="modalTitle"></h5> | |
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> | |
</div> | |
<div class="modal-body" id="modalBody"> | |
<!-- Content will be loaded here --> | |
<div class="text-center p-5" id="modalLoading"> | |
<div class="spinner"></div> | |
<p class="mt-3">Loading content...</p> | |
</div> | |
</div> | |
<div class="modal-footer"> | |
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Back to top button --> | |
<button id="backToTop" title="Back to top"> | |
<i class="fas fa-chevron-up"></i> | |
</button> | |
</div> | |
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script> | |
<script> | |
// Initialize modal | |
const detailModal = new bootstrap.Modal(document.getElementById('detailModal')); | |
let loadedDetails = {}; | |
// Language selector | |
document.getElementById('languageSelect').addEventListener('change', function() { | |
const selectedLanguage = this.value; | |
// Show loading spinner | |
const loadingElement = document.createElement('div'); | |
loadingElement.id = 'pageLoading'; | |
loadingElement.className = 'position-fixed top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center bg-white bg-opacity-75'; | |
loadingElement.style.zIndex = '9999'; | |
loadingElement.innerHTML = '<div class="spinner"></div><p class="mt-3">Changing language...</p>'; | |
document.body.appendChild(loadingElement); | |
// Send request to change language | |
fetch('/set_language', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/x-www-form-urlencoded', | |
}, | |
body: `language=${selectedLanguage}`, | |
credentials: 'same-origin' // Ensure cookies are sent with the request | |
}) | |
.then(response => response.json()) | |
.then(data => { | |
if (data.success) { | |
// Clear loaded details cache | |
loadedDetails = {}; | |
// Store language in localStorage as a fallback | |
localStorage.setItem('preferredLanguage', selectedLanguage); | |
// Refresh the page | |
window.location.reload(); | |
} | |
}) | |
.catch(error => { | |
console.error('Error:', error); | |
document.body.removeChild(loadingElement); | |
alert('Error changing language. Please try again.'); | |
}); | |
}); | |
// Check if we need to restore language preference from localStorage | |
document.addEventListener('DOMContentLoaded', function() { | |
const storedLanguage = localStorage.getItem('preferredLanguage'); | |
const currentLanguage = document.getElementById('languageSelect').value; | |
if (storedLanguage && storedLanguage !== currentLanguage) { | |
// If the stored language differs from the current one, set it | |
document.getElementById('languageSelect').value = storedLanguage; | |
// Trigger the change event | |
document.getElementById('languageSelect').dispatchEvent(new Event('change')); | |
} | |
}); | |
// Lazy load images | |
document.addEventListener("DOMContentLoaded", function() { | |
const lazyImages = document.querySelectorAll(".lazy-load"); | |
if ("IntersectionObserver" in window) { | |
const imageObserver = new IntersectionObserver(function(entries, observer) { | |
entries.forEach(function(entry) { | |
if (entry.isIntersecting) { | |
const img = entry.target; | |
img.src = img.dataset.src; | |
img.classList.remove("lazy-load"); | |
imageObserver.unobserve(img); | |
} | |
}); | |
}); | |
lazyImages.forEach(function(image) { | |
imageObserver.observe(image); | |
}); | |
} else { | |
// Fallback for browsers without intersection observer | |
lazyImages.forEach(function(img) { | |
img.src = img.dataset.src; | |
}); | |
} | |
}); | |
// Filter functionality | |
document.querySelectorAll('.filter-btn').forEach(button => { | |
button.addEventListener('click', function() { | |
document.querySelectorAll('.filter-btn').forEach(btn => { | |
btn.classList.remove('active'); | |
}); | |
this.classList.add('active'); | |
const filterValue = this.getAttribute('data-filter'); | |
filterItems(filterValue, document.getElementById('searchInput').value); | |
}); | |
}); | |
// Search functionality | |
document.getElementById('searchInput').addEventListener('input', function() { | |
const activeFilter = document.querySelector('.filter-btn.active').getAttribute('data-filter'); | |
filterItems(activeFilter, this.value); | |
}); | |
function filterItems(typeFilter, searchTerm) { | |
searchTerm = searchTerm.toLowerCase(); | |
let visibleCount = 0; | |
document.querySelectorAll('.item-card').forEach(item => { | |
const type = item.getAttribute('data-type'); | |
const name = item.getAttribute('data-name'); | |
const crop = item.getAttribute('data-crop'); | |
const typeMatch = typeFilter === 'all' || type === typeFilter; | |
const searchMatch = !searchTerm || | |
name.includes(searchTerm) || | |
crop.includes(searchTerm); | |
if (typeMatch && searchMatch) { | |
item.style.display = 'block'; | |
visibleCount++; | |
} else { | |
item.style.display = 'none'; | |
} | |
}); | |
// Show/hide no results message | |
document.getElementById('noResults').style.display = visibleCount === 0 ? 'block' : 'none'; | |
} | |
// Function to show details | |
function showDetails(id) { | |
// Clear previous content and show loading | |
document.getElementById('modalTitle').textContent = ""; | |
document.getElementById('modalBody').innerHTML = ` | |
<div class="text-center p-5" id="modalLoading"> | |
<div class="spinner"></div> | |
<p class="mt-3">Loading content...</p> | |
</div> | |
`; | |
// Show modal immediately with loading indicator | |
detailModal.show(); | |
// Check if we already have the details cached | |
if (loadedDetails[id]) { | |
displayDetails(loadedDetails[id]); | |
return; | |
} | |
// Fetch details from the backend | |
fetch(`/get_details/${id}`, { | |
credentials: 'same-origin' // Ensure cookies are sent with the request | |
}) | |
.then(response => { | |
if (!response.ok) { | |
throw new Error('Network response was not ok'); | |
} | |
return response.json(); | |
}) | |
.then(data => { | |
// Cache the result | |
loadedDetails[id] = data; | |
displayDetails(data); | |
}) | |
.catch(error => { | |
console.error('Error:', error); | |
document.getElementById('modalBody').innerHTML = ` | |
<div class="alert alert-danger" role="alert"> | |
<i class="fas fa-exclamation-circle"></i> | |
Error loading information. Please try again. | |
</div> | |
`; | |
}); | |
} | |
function displayDetails(data) { | |
// Update modal title | |
document.getElementById('modalTitle').textContent = data.name; | |
// Format the details | |
const details = data.details; | |
let content = ` | |
<div class="text-center mb-4"> | |
<img src="${data.image_url}" alt="${data.name}" class="img-fluid rounded" style="max-height: 300px;"> | |
<div class="mt-2"> | |
<span class="badge ${data.type} mb-2">${data.type.charAt(0).toUpperCase() + data.type.slice(1)}</span> | |
<div> | |
<strong>Affects:</strong> | |
${data.crop.split(', ').map(crop => `<span class="badge crop-badge">${crop}</span>`).join(' ')} | |
</div> | |
</div> | |
</div> | |
`; | |
// Add each section | |
const sections = [ | |
{key: 'description', icon: 'info-circle', title: details.description.title || 'Description and Identification'}, | |
{key: 'lifecycle', icon: 'sync', title: details.lifecycle.title || 'Lifecycle and Spread'}, | |
{key: 'symptoms', icon: 'exclamation-triangle', title: details.symptoms.title || 'Damage Symptoms'}, | |
{key: 'impact', icon: 'chart-line', title: details.impact.title || 'Economic Impact'}, | |
{key: 'management', icon: 'tasks', title: details.management.title || 'Management and Control'}, | |
{key: 'prevention', icon: 'shield-alt', title: details.prevention.title || 'Prevention'} | |
]; | |
sections.forEach(section => { | |
if (details[section.key] && details[section.key].text) { | |
content += ` | |
<h4 class="section-title"><i class="fas fa-${section.icon} me-2"></i>${section.title}</h4> | |
<p>${details[section.key].text}</p> | |
`; | |
} | |
}); | |
document.getElementById('modalBody').innerHTML = content; | |
} | |
// Back to top button functionality | |
window.onscroll = function() { | |
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) { | |
document.getElementById("backToTop").style.display = "block"; | |
} else { | |
document.getElementById("backToTop").style.display = "none"; | |
} | |
}; | |
document.getElementById("backToTop").addEventListener("click", function() { | |
window.scrollTo({top: 0, behavior: 'smooth'}); | |
}); | |
</script> | |
</body> | |
</html> |