Spaces:
Sleeping
Sleeping
| <html lang="{{ language_code }}"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Pest Prediction Intelligence</title> | |
| <!-- Fonts --> | |
| <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap" | |
| rel="stylesheet"> | |
| <!-- Bootstrap 5 --> | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <!-- Icons --> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> | |
| <style> | |
| /* --- PROFESSIONAL UI SYSTEM --- */ | |
| :root { | |
| --primary: #166534; | |
| --surface: #ffffff; | |
| --surface-subtle: #f8fafc; | |
| --border-light: #e2e8f0; | |
| --text-main: #1e293b; | |
| --text-muted: #64748b; | |
| --radius-lg: 24px; | |
| --radius-md: 16px; | |
| --shadow-soft: 0 20px 40px -10px rgba(0, 0, 0, 0.1); | |
| --shadow-crisp: 0 4px 6px -1px rgba(0, 0, 0, 0.05); | |
| } | |
| body { | |
| font-family: 'Outfit', sans-serif; | |
| background-color: var(--surface-subtle); | |
| color: var(--text-main); | |
| padding-bottom: 5rem; | |
| overflow-x: hidden; | |
| } | |
| /* Hero */ | |
| .hero-banner { | |
| background: linear-gradient(135deg, #166534 0%, #14532d 100%); | |
| color: white; | |
| padding: 3rem 1rem 6rem; | |
| position: relative; | |
| margin-bottom: -4rem; | |
| } | |
| .report-meta-badge { | |
| background: rgba(255, 255, 255, 0.15); | |
| backdrop-filter: blur(5px); | |
| padding: 0.5rem 1rem; | |
| border-radius: 50px; | |
| font-size: 0.9rem; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| /* Container */ | |
| .content-wrapper { | |
| max-width: 1100px; | |
| margin: 0 auto; | |
| position: relative; | |
| z-index: 20; | |
| padding: 0 1.5rem; | |
| } | |
| /* 1. AUDIO STRIP */ | |
| .audio-sketch-box { | |
| background: var(--surface); | |
| border-radius: var(--radius-md); | |
| padding: 1rem 1.5rem; | |
| margin-bottom: 2rem; | |
| box-shadow: var(--shadow-crisp); | |
| border: 1px solid var(--border-light); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| } | |
| .audio-icon-circle { | |
| width: 48px; | |
| height: 48px; | |
| background: #f0fdf4; | |
| color: var(--primary); | |
| border-radius: 50%; | |
| display: grid; | |
| place-items: center; | |
| font-size: 1.25rem; | |
| } | |
| /* 2. CALENDAR FLIPBOOK */ | |
| .flipbook-wrapper { | |
| position: relative; | |
| max-width: 1000px; | |
| margin: 0 auto; | |
| perspective: 2000px; | |
| display: flex; | |
| align-items: center; | |
| gap: 1.5rem; | |
| } | |
| .flipbook-stage { | |
| width: 100%; | |
| height: 640px; | |
| /* Fixed Optimal Height */ | |
| position: relative; | |
| } | |
| .flip-card { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: var(--surface); | |
| border-radius: var(--radius-lg); | |
| border: 1px solid var(--border-light); | |
| box-shadow: var(--shadow-soft); | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| /* Transitions */ | |
| opacity: 0; | |
| transform: translateX(50px) scale(0.95); | |
| pointer-events: none; | |
| transition: all 0.5s cubic-bezier(0.2, 0.8, 0.2, 1); | |
| } | |
| .flip-card.active { | |
| opacity: 1; | |
| transform: translateX(0) scale(1); | |
| pointer-events: all; | |
| z-index: 10; | |
| } | |
| /* HEADER: Stats Enhanced */ | |
| .cal-header { | |
| background: white; | |
| border-bottom: 1px solid var(--border-light); | |
| padding: 1.2rem 2rem; | |
| height: 100px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| } | |
| .month-title { | |
| font-size: 2.2rem; | |
| font-weight: 800; | |
| color: #0f172a; | |
| margin: 0; | |
| letter-spacing: -1px; | |
| } | |
| .stats-row { | |
| display: flex; | |
| gap: 0.75rem; | |
| } | |
| .stat-badge { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-start; | |
| background: #f8fafc; | |
| border: 1px solid #e2e8f0; | |
| padding: 0.4rem 0.8rem; | |
| border-radius: 8px; | |
| min-width: 80px; | |
| } | |
| .stat-label { | |
| font-size: 0.65rem; | |
| text-transform: uppercase; | |
| color: #64748b; | |
| font-weight: 700; | |
| } | |
| .stat-val { | |
| font-size: 0.95rem; | |
| font-weight: 700; | |
| color: #334155; | |
| } | |
| .stat-val i { | |
| margin-right: 4px; | |
| } | |
| /* BODY SPLIT */ | |
| .cal-body { | |
| flex-grow: 1; | |
| display: flex; | |
| overflow: hidden; | |
| } | |
| /* LEFT: RISKS */ | |
| .cal-left { | |
| width: 35%; | |
| background: #fdfdfd; | |
| border-right: 1px solid var(--border-light); | |
| padding: 1.5rem; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .section-head { | |
| font-size: 0.7rem; | |
| font-weight: 800; | |
| letter-spacing: 1px; | |
| color: #94a3b8; | |
| text-transform: uppercase; | |
| margin-bottom: 1rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .threat-list { | |
| overflow-y: auto; | |
| flex-grow: 1; | |
| padding-right: 0.5rem; | |
| } | |
| .threat-item { | |
| background: white; | |
| border: 1px solid var(--border-light); | |
| padding: 0.9rem; | |
| border-radius: 12px; | |
| margin-bottom: 0.75rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| transition: 0.2s; | |
| } | |
| .threat-item:hover { | |
| border-color: #94a3b8; | |
| transform: translateX(2px); | |
| } | |
| .risk-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| display: block; | |
| } | |
| .risk-dot.high { | |
| background: #ef4444; | |
| box-shadow: 0 0 0 3px #fef2f2; | |
| } | |
| .risk-dot.medium { | |
| background: #f97316; | |
| } | |
| /* RIGHT: STRATEGY */ | |
| .cal-right { | |
| width: 65%; | |
| background: white; | |
| padding: 2rem; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| /* 1. Overview Grid */ | |
| .overview-panel { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1rem; | |
| margin-bottom: 2rem; | |
| } | |
| .insight-card { | |
| background: #f8fafc; | |
| border: 1px solid #e2e8f0; | |
| padding: 1rem; | |
| border-radius: 12px; | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| .ic-icon { | |
| font-size: 1.5rem; | |
| } | |
| .ic-content { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .ic-label { | |
| font-size: 0.7rem; | |
| font-weight: 700; | |
| color: #64748b; | |
| text-transform: uppercase; | |
| } | |
| .ic-value { | |
| font-size: 0.95rem; | |
| font-weight: 800; | |
| color: #0f172a; | |
| } | |
| /* Critical Logic */ | |
| .insight-card.critical { | |
| background: #fff1f2; | |
| border-color: #ffe4e6; | |
| } | |
| .insight-card.critical .ic-icon { | |
| color: #e11d48; | |
| } | |
| .insight-card.critical .ic-value { | |
| color: #881337; | |
| } | |
| .insight-card.advisory { | |
| background: #eff6ff; | |
| border-color: #dbeafe; | |
| } | |
| .insight-card.advisory .ic-icon { | |
| color: #2563eb; | |
| } | |
| /* 2. Tasks */ | |
| .task-scroll { | |
| flex-grow: 1; | |
| overflow-y: auto; | |
| padding-right: 0.5rem; | |
| border-top: 1px dashed var(--border-light); | |
| padding-top: 1.5rem; | |
| } | |
| .task-row { | |
| display: flex; | |
| gap: 1rem; | |
| margin-bottom: 1.25rem; | |
| } | |
| .t-check { | |
| width: 20px; | |
| height: 20px; | |
| border: 2px solid #cbd5e1; | |
| border-radius: 6px; | |
| flex-shrink: 0; | |
| margin-top: 2px; | |
| } | |
| .task-row.urgent .t-check { | |
| border-color: #ef4444; | |
| background: #fee2e2; | |
| } | |
| .t-text { | |
| font-size: 0.95rem; | |
| color: #334155; | |
| line-height: 1.5; | |
| margin-bottom: 0.25rem; | |
| } | |
| .t-tag { | |
| background: #f1f5f9; | |
| color: #64748b; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| font-size: 0.7rem; | |
| font-weight: 700; | |
| } | |
| /* Footer */ | |
| .stage-footer { | |
| margin-top: 1rem; | |
| padding-top: 1rem; | |
| border-top: 1px solid var(--border-light); | |
| display: flex; | |
| gap: 0.5rem; | |
| flex-wrap: wrap; | |
| align-items: center; | |
| } | |
| .stage-pill { | |
| font-size: 0.75rem; | |
| font-weight: 700; | |
| color: #b45309; | |
| background: #fffbeb; | |
| border: 1px solid #fcd34d; | |
| padding: 0.25rem 0.75rem; | |
| border-radius: 20px; | |
| } | |
| /* Nav Buttons */ | |
| .nav-btn { | |
| width: 50px; | |
| height: 50px; | |
| border-radius: 50%; | |
| background: #1e293b; | |
| color: white; | |
| border: none; | |
| display: grid; | |
| place-items: center; | |
| font-size: 1.25rem; | |
| cursor: pointer; | |
| transition: 0.2s; | |
| box-shadow: 0 8px 20px -5px rgba(0, 0, 0, 0.3); | |
| } | |
| .nav-btn:hover { | |
| transform: scale(1.1); | |
| background: #0f172a; | |
| } | |
| @media (max-width: 900px) { | |
| .flipbook-stage { | |
| height: auto; | |
| } | |
| .flip-card { | |
| position: relative; | |
| opacity: 1; | |
| transform: none; | |
| display: block; | |
| margin-bottom: 2rem; | |
| height: auto; | |
| } | |
| .cal-body { | |
| flex-direction: column; | |
| } | |
| .cal-left { | |
| width: 100%; | |
| height: 300px; | |
| border-right: none; | |
| border-bottom: 1px solid var(--border-light); | |
| } | |
| .cal-right { | |
| width: 100%; | |
| border: none; | |
| } | |
| .overview-panel { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="hero-banner"> | |
| <div class="container text-center"> | |
| <h1 class="display-4 fw-bold mb-3">{{ report_data.report_title }}</h1> | |
| <div class="d-flex justify-content-center gap-3"> | |
| <span class="report-meta-badge"><i class="bi bi-geo-alt-fill"></i> {{ location.derived_location | |
| }}</span> | |
| <span class="report-meta-badge"><i class="bi bi-calendar-check"></i> {{ current_date }}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="content-wrapper"> | |
| <!-- AUDIO BOX --> | |
| {% if audio_url %} | |
| <div class="audio-sketch-box"> | |
| <div class="d-flex align-items-center gap-3"> | |
| <div class="audio-icon-circle"><i class="bi bi-mic-fill"></i></div> | |
| <div> | |
| <h5 class="fw-bold mb-0 text-dark">Field Briefing</h5> | |
| <small class="text-muted">Executive Summary • {{ language_code|upper }}</small> | |
| </div> | |
| </div> | |
| <audio controls style="height: 36px; width: 250px; border-radius: 20px;"> | |
| <source src="{{ audio_url }}" type="audio/mpeg"> | |
| </audio> | |
| </div> | |
| {% endif %} | |
| <!-- CALENDAR --> | |
| <div class="flipbook-wrapper"> | |
| <button class="nav-btn" onclick="changeMonth(-1)"><i class="bi bi-chevron-left"></i></button> | |
| <div id="flipbookStage" class="flipbook-stage"> | |
| {% if weather_profile %} | |
| {% for w in weather_profile %} | |
| <div class="flip-card {{ 'active' if loop.index0 == 0 else '' }}"> | |
| <!-- Header with ENHANCED STATS --> | |
| <div class="cal-header"> | |
| <h2 class="month-title">{{ w.month }}</h2> | |
| <div class="stats-row"> | |
| <div class="stat-badge"> | |
| <span class="stat-label">Avg Temp</span> | |
| <span class="stat-val"><i class="bi bi-thermometer-half text-danger"></i> {{ w.avg_temp | |
| }}°</span> | |
| </div> | |
| <div class="stat-badge"> | |
| <span class="stat-label">Rainfall</span> | |
| <span class="stat-val"><i class="bi bi-droplet-fill text-primary"></i> {{ w.rainfall | |
| }}mm</span> | |
| </div> | |
| <!-- NEW: Humidity --> | |
| <div class="stat-badge"> | |
| <span class="stat-label">Humidity</span> | |
| <span class="stat-val"><i class="bi bi-moisture text-info"></i> {{ w.humidity }}%</span> | |
| </div> | |
| <!-- NEW: Rainy Days Calculation --> | |
| {% set rainy_days = w.days | selectattr('rain', '>', 0) | list | length %} | |
| <div class="stat-badge"> | |
| <span class="stat-label">Rainy Days</span> | |
| <span class="stat-val"><i class="bi bi-cloud-drizzle-fill text-dark"></i> {{ rainy_days | |
| }}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="cal-body"> | |
| <!-- Left: Threats --> | |
| <div class="cal-left"> | |
| <div class="section-head"><i class="bi bi-bug"></i> Identified Risks</div> | |
| <div class="threat-list"> | |
| {% set ns = namespace(found=false) %} | |
| {% for pest in report_data.pest_prediction_table %} | |
| {% set current_month_name = w.month.split(' ')[0] %} | |
| {% if current_month_name in pest.outbreak_months or "All" in pest.outbreak_months %} | |
| {% set ns.found = true %} | |
| <div class="threat-item"> | |
| <span class="fw-bold text-dark">{{ pest.pest_name }}</span> | |
| <span class="risk-dot {{ pest.severity|lower }}"></span> | |
| </div> | |
| {% endif %} | |
| {% endfor %} | |
| {% if not ns.found %} | |
| <div class="text-center py-5 text-muted opacity-50"> | |
| <i class="bi bi-shield-check fs-1"></i> | |
| <p class="small mt-2">Zero Threats</p> | |
| </div> | |
| {% endif %} | |
| </div> | |
| </div> | |
| <!-- Right: Strategy --> | |
| <div class="cal-right"> | |
| <!-- Overview --> | |
| <div class="overview-panel"> | |
| {% set ns_meta = namespace(has_high=false, stages=[]) %} | |
| {% for pest in report_data.pest_prediction_table %} | |
| {% set current_month_name = w.month.split(' ')[0] %} | |
| {% if current_month_name in pest.outbreak_months or "All" in pest.outbreak_months %} | |
| {% if 'High' in pest.severity %} {% set ns_meta.has_high = true %} {% endif %} | |
| {% if pest.impacting_stage and pest.impacting_stage not in ns_meta.stages %} | |
| {% set _ = ns_meta.stages.append(pest.impacting_stage) %} | |
| {% endif %} | |
| {% endif %} | |
| {% endfor %} | |
| <!-- Status Card --> | |
| <div class="insight-card {{ 'critical' if ns_meta.has_high else 'stable' }}"> | |
| <div class="ic-icon"><i | |
| class="bi {{ 'bi-exclamation-octagon-fill' if ns_meta.has_high else 'bi-check-circle-fill text-success' }}"></i> | |
| </div> | |
| <div class="ic-content"> | |
| <span class="ic-label">Threat Level</span> | |
| <span class="ic-value">{{ 'CRITICAL' if ns_meta.has_high else 'MONITORING' | |
| }}</span> | |
| </div> | |
| </div> | |
| <!-- Weather Tip Card --> | |
| <div class="insight-card advisory"> | |
| <div class="ic-icon"><i class="bi bi-lightbulb-fill"></i></div> | |
| <div class="ic-content"> | |
| <span class="ic-label">Advisory</span> | |
| <span class="ic-value" style="font-size: 0.85rem; font-weight: 600;"> | |
| {% if w.rainfall > 150 %} Ensure Drainage | |
| {% elif w.rainfall < 10 %} Increase Irrigation {% elif w.avg_temp> 35 %} | |
| Heat Protection | |
| {% else %} Standard Monitoring {% endif %} | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Tasks --> | |
| <div class="section-head text-dark"><i class="bi bi-list-check"></i> Action Checklist</div> | |
| <div class="task-scroll"> | |
| {% set ns_act = namespace(found=false) %} | |
| {% for pest in report_data.pest_prediction_table %} | |
| {% set current_month_name = w.month.split(' ')[0] %} | |
| {% if current_month_name in pest.outbreak_months or "All" in pest.outbreak_months %} | |
| {% set ns_act.found = true %} | |
| <div class="task-row {{ 'urgent' if 'High' in pest.severity else '' }}"> | |
| <div class="t-check"></div> | |
| <div> | |
| <div class="t-text">{{ pest.precautionary_measures | striptags }}</div> | |
| <span class="t-tag">Preventing: {{ pest.pest_name }}</span> | |
| </div> | |
| </div> | |
| {% endif %} | |
| {% endfor %} | |
| {% if not ns_act.found %} | |
| <div class="task-row"> | |
| <div class="t-check bg-success border-success"></div> | |
| <div class="t-text text-success">All clear. Proceed with standard crop husbandry. | |
| </div> | |
| </div> | |
| {% endif %} | |
| </div> | |
| <!-- Footer --> | |
| {% if ns_meta.stages %} | |
| <div class="stage-footer"> | |
| <span class="small fw-bold text-muted">VULNERABLE STAGES:</span> | |
| {% for stage in ns_meta.stages %} | |
| <span class="stage-pill">{{ stage }}</span> | |
| {% endfor %} | |
| </div> | |
| {% endif %} | |
| </div> | |
| </div> | |
| </div> | |
| {% endfor %} | |
| {% else %} | |
| <div class="alert alert-danger">Loading Data...</div> | |
| {% endif %} | |
| </div> | |
| <button class="nav-btn" onclick="changeMonth(1)"><i class="bi bi-chevron-right"></i></button> | |
| </div> | |
| <div class="text-center mt-4 mb-5"> | |
| <span id="pIndicator" class="badge rounded-pill bg-dark py-2 px-4 shadow">Month 1</span> | |
| </div> | |
| <div class="text-center pb-5"> | |
| <a href="/" class="btn btn-outline-dark rounded-pill px-4"><i class="bi bi-arrow-repeat me-2"></i>Analyze | |
| Another Field</a> | |
| <div class="mt-4 text-muted small opacity-75"> | |
| Powered by Gemini & NVIDIA AI Intelligence | |
| </div> | |
| </div> | |
| </div> | |
| <!-- JS --> | |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> | |
| <script> | |
| let curr = 0; | |
| const cards = document.querySelectorAll('.flip-card'); | |
| const ind = document.getElementById('pIndicator'); | |
| const len = cards.length; | |
| function render() { | |
| cards.forEach((c, i) => { | |
| c.classList.remove('active'); | |
| if (i === curr) c.classList.add('active'); | |
| }); | |
| if (len > 0) ind.innerText = `Month ${curr + 1} of ${len}`; | |
| } | |
| function changeMonth(d) { | |
| let n = curr + d; | |
| if (n >= 0 && n < len) { curr = n; render(); } | |
| } | |
| render(); | |
| </script> | |
| </body> | |
| </html> |