Pestipedia / templates /index.html
pranit144's picture
Update templates/index.html
33cd36a verified
<!DOCTYPE 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; }
.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 {
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>
<div class="language-selector mt-3">
<form id="languageForm" method="POST" action="{{ url_for('set_language') }}" class="d-flex justify-content-center align-items-center">
<label for="languageSelect" class="me-2 text-white"><i class="fas fa-language"></i> Language:</label>
<select id="languageSelect" name="language" class="form-select form-select-sm" style="max-width: 220px;" onchange="this.form.submit()">
{% for code, name in languages.items() %}
<option value="{{ code }}" {% if code == current_language %}selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
</form>
</div>
</header>
<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>
<!-- 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">
<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>
<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>
const detailModal = new bootstrap.Modal(document.getElementById('detailModal'));
let loadedDetails = {};
document.getElementById('languageSelect').addEventListener('change', function() {
// Clear client-side cache to force reload in new language
loadedDetails = {};
});
// 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 {
lazyImages.forEach(function(img) { img.src = img.dataset.src; });
}
});
// Filter & search logic
const searchInput = document.getElementById('searchInput');
const filterButtons = document.querySelectorAll('.filter-btn');
function filterItems() {
const searchTerm = searchInput.value.toLowerCase();
const activeFilter = document.querySelector('.filter-btn.active').getAttribute('data-filter');
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 = activeFilter === 'all' || type === activeFilter;
const searchMatch = !searchTerm || name.includes(searchTerm) || crop.includes(searchTerm);
if (typeMatch && searchMatch) {
item.style.display = 'block';
visibleCount++;
} else {
item.style.display = 'none';
}
});
document.getElementById('noResults').style.display = visibleCount === 0 ? 'block' : 'none';
}
filterButtons.forEach(button => {
button.addEventListener('click', function() {
filterButtons.forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
filterItems();
});
});
searchInput.addEventListener('input', filterItems);
// Show details modal
function showDetails(id) {
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>`;
detailModal.show();
const currentLanguage = document.getElementById('languageSelect').value;
const cacheKey = `${id}-${currentLanguage}`;
if (loadedDetails[cacheKey]) {
displayDetails(loadedDetails[cacheKey]);
return;
}
fetch(`/get_details/${id}`)
.then(response => {
if (!response.ok) { throw new Error(`Network response was not ok (${response.status})`); }
return response.json();
})
.then(data => {
if (data.error) { throw new Error(data.message || data.error); }
loadedDetails[cacheKey] = 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 check the server logs and ensure the API key is valid.</div>`;
});
}
function displayDetails(data) {
document.getElementById('modalTitle').textContent = data.name;
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>`;
const sections = [
{key: 'description', icon: 'info-circle', title: details.description.title || 'Description'},
{key: 'lifecycle', icon: 'sync', title: details.lifecycle.title || 'Lifecycle'},
{key: 'symptoms', icon: 'exclamation-triangle', title: details.symptoms.title || 'Symptoms'},
{key: 'impact', icon: 'chart-line', title: details.impact.title || 'Economic Impact'},
{key: 'management', icon: 'tasks', title: details.management.title || 'Management'},
{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.replace(/\n/g, '<br>')}</p>`;
}
});
document.getElementById('modalBody').innerHTML = content;
}
// Back to top
const backToTopButton = document.getElementById("backToTop");
window.onscroll = () => {
backToTopButton.style.display = (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) ? "block" : "none";
};
backToTopButton.addEventListener("click", () => {
window.scrollTo({top: 0, behavior: 'smooth'});
});
</script>
</body>
</html>