Spaces:
Sleeping
Sleeping
| <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> |