pranit144's picture
Update templates/index.html
aa9649a verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Modern Weather Dashboard</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet" />
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-green: #10b981;
--light-green: #d1fae5;
--dark-green: #065f46;
--gradient-start: #059669;
--gradient-end: #047857;
}
body {
font-family: 'Plus Jakarta Sans', sans-serif;
background: #f0fdf4;
color: #1f2937;
line-height: 1.5;
}
/* Animations */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
/* Layout */
.container {
display: flex;
min-height: 100vh;
flex-direction: row; /* Default for larger screens */
}
/* Sidebar */
.sidebar {
width: 300px;
background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
padding: 2rem;
color: white;
position: fixed; /* Fixed for larger screens */
height: 100vh;
overflow-y: auto;
animation: slideInLeft 0.5s ease-out;
box-shadow: 4px 0 15px rgba(0,0,0,0.1);
z-index: 10; /* Ensure sidebar is above main content if needed */
}
.sidebar-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid rgba(255,255,255,0.2);
}
.sidebar-header i {
font-size: 1.5rem;
}
.input-group {
margin-bottom: 1.5rem;
}
.input-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
font-size: 0.9rem;
}
.input-field {
width: 100%;
padding: 0.75rem;
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.2);
background: rgba(255,255,255,0.1);
color: white;
transition: all 0.3s ease;
}
.input-field::placeholder {
color: rgba(255,255,255,0.7);
}
.input-field:focus {
outline: none;
border-color: rgba(255,255,255,0.5);
background: rgba(255,255,255,0.15);
}
.search-button {
width: 100%;
padding: 0.75rem;
border-radius: 8px;
background: white;
color: var(--dark-green);
font-weight: 600;
border: none;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.search-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
/* Main Content */
.main-content {
flex: 1;
margin-left: 300px; /* Offset for sidebar */
padding: 2rem;
animation: fadeInUp 0.5s ease-out;
}
/* Header: Current Conditions */
.header-card {
background: white;
border-radius: 16px;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.05);
animation: fadeInUp 0.5s ease-out;
display: flex;
flex-wrap: wrap; /* Allow items to wrap */
justify-content: space-between;
align-items: center;
}
.header-location {
font-size: 1.5rem;
font-weight: 700;
color: var(--dark-green);
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem; /* Add margin for stacking */
}
.timestamp {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--light-green);
border-radius: 8px;
font-size: 0.9rem;
color: var(--dark-green);
margin-bottom: 1rem; /* Add margin for stacking */
}
.current-temp {
font-size: 3.5rem;
font-weight: 700;
color: var(--dark-green);
display: flex;
align-items: center;
gap: 1rem;
}
.weather-icon {
font-size: 3rem;
color: var(--primary-green);
}
.weather-details {
display: flex;
gap: 2rem;
margin-top: 1rem;
flex-wrap: wrap; /* Allow details to wrap */
}
.weather-detail {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--dark-green);
}
/* Alert Notification */
.alert-notification {
animation: fadeInUp 0.5s ease-out;
border-radius: 8px; /* Added border-radius */
}
/* Highlights Grid */
.highlights {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.highlight-card {
background: white;
border-radius: 16px;
padding: 1.5rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
transition: all 0.3s ease;
animation: fadeInUp 0.5s ease-out;
}
.highlight-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
}
.highlight-icon {
font-size: 2rem;
color: var(--primary-green);
margin-bottom: 1rem;
}
.highlight-title {
font-size: 1rem;
font-weight: 600;
color: var(--dark-green);
margin-bottom: 0.5rem;
}
.highlight-value {
font-size: 1.75rem;
font-weight: 700;
color: var(--primary-green);
}
/* Forecast Section */
.forecast-section {
margin-top: 2rem;
}
.section-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--dark-green);
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.forecast-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1.5rem;
}
.forecast-card {
background: white;
border-radius: 16px;
padding: 1.5rem;
text-align: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
transition: all 0.3s ease;
animation: fadeInUp 0.5s ease-out;
}
.forecast-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
cursor: pointer;
}
.forecast-day {
font-weight: 700;
color: var(--dark-green);
font-size: 1.1rem;
margin-bottom: 0.5rem;
}
.forecast-date {
color: #6b7280;
font-size: 0.9rem;
margin-bottom: 1rem;
}
/* Conditional Forecast Icon */
.forecast-icon {
font-size: 2.5rem;
margin: 1rem 0;
}
.forecast-temp {
font-size: 1.5rem;
font-weight: 700;
color: var(--primary-green);
margin: 0.5rem 0;
}
.temp-range {
display: flex;
justify-content: center;
gap: 1rem;
color: #6b7280;
font-size: 0.9rem;
}
.sunrise-sunset {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #e5e7eb;
font-size: 0.9rem;
color: #6b7280;
}
/* Footer */
.footer-note {
margin-top: 2rem;
color: #777;
font-size: 0.8rem;
text-align: center;
}
/* --- Responsive Design --- */
/* Medium screens (e.g., small laptops, large tablets) */
@media (max-width: 1024px) {
.sidebar {
width: 260px;
padding: 1.5rem;
}
.main-content {
margin-left: 260px;
padding: 1.5rem;
}
.header-location {
font-size: 1.3rem;
}
.current-temp {
font-size: 3rem;
}
.weather-icon {
font-size: 2.5rem;
}
.highlight-value {
font-size: 1.5rem;
}
.section-title {
font-size: 1.3rem;
}
.forecast-temp {
font-size: 1.3rem;
}
}
/* Small screens (e.g., tablets in portrait, large phones) */
@media (max-width: 768px) {
.container {
flex-direction: column; /* Stack sidebar and main content vertically */
}
.sidebar {
width: 100%; /* Full width */
height: auto; /* Auto height */
position: relative; /* No longer fixed */
padding: 1.5rem 1rem; /* Adjust padding */
box-shadow: 0 4px 15px rgba(0,0,0,0.1); /* Add bottom shadow */
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.main-content {
margin-left: 0; /* Remove sidebar offset */
padding: 1.5rem 1rem; /* Adjust padding */
}
.sidebar-header {
margin-bottom: 1.5rem;
justify-content: center; /* Center header on mobile */
}
.input-group {
margin-bottom: 1rem;
}
.search-button {
margin-top: 1rem; /* Add some spacing */
}
.header-card {
flex-direction: column; /* Stack items in header card */
align-items: flex-start; /* Align left */
padding: 1.5rem;
}
.header-location, .timestamp {
margin-bottom: 0.75rem; /* Adjust spacing */
width: 100%; /* Full width for better stacking */
}
.header-location {
font-size: 1.2rem;
}
.current-temp {
font-size: 2.5rem;
gap: 0.75rem;
margin-top: 1rem;
flex-wrap: wrap; /* Allow temp and icon to wrap if needed */
}
.weather-icon {
font-size: 2.2rem;
}
.header-card .flex.items-center.gap-4 { /* Target the div containing current temp and description */
flex-direction: column; /* Stack current temp and description */
align-items: flex-start;
width: 100%;
margin-top: 1rem;
}
.header-card .text-right { /* Adjust description alignment */
text-align: left;
width: 100%;
margin-top: 0.5rem;
}
.weather-details {
flex-direction: column;
gap: 0.75rem;
margin-top: 0.75rem;
}
.highlights {
grid-template-columns: 1fr; /* Single column for highlights on smaller screens */
gap: 1rem;
}
.forecast-grid {
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); /* Adjust minwidth for forecast */
gap: 1rem;
}
.highlight-icon {
font-size: 1.75rem;
margin-bottom: 0.75rem;
}
.highlight-value {
font-size: 1.4rem;
}
.section-title {
font-size: 1.2rem;
margin-bottom: 1rem;
}
.forecast-card {
padding: 1rem;
}
.forecast-icon {
font-size: 2rem;
}
.forecast-day {
font-size: 1rem;
}
.forecast-temp {
font-size: 1.2rem;
}
.footer-note {
margin-top: 1.5rem;
}
}
/* Extra small screens (e.g., mobile phones) */
@media (max-width: 480px) {
.sidebar {
padding: 1rem;
}
.main-content {
padding: 1rem;
}
.sidebar-header {
margin-bottom: 1rem;
}
.sidebar-header h1 {
font-size: 1.2rem;
}
.header-location {
font-size: 1rem;
}
.timestamp {
font-size: 0.8rem;
padding: 0.4rem 0.8rem;
}
.current-temp {
font-size: 2rem;
}
.weather-icon {
font-size: 1.8rem;
}
.header-card .text-right .text-lg { /* Current description text */
font-size: 1rem;
}
.weather-detail {
font-size: 0.9rem;
}
.highlight-card {
padding: 1.2rem;
}
.highlight-icon {
font-size: 1.5rem;
}
.highlight-title {
font-size: 0.9rem;
}
.highlight-value {
font-size: 1.2rem;
}
.section-title {
font-size: 1.1rem;
}
.forecast-grid {
grid-template-columns: 1fr; /* Single column for forecast on very small screens */
}
.forecast-icon {
font-size: 1.8rem;
}
.forecast-temp {
font-size: 1.1rem;
}
.temp-range, .sunrise-sunset {
font-size: 0.85rem;
}
.footer-note {
font-size: 0.75rem;
margin-top: 1rem;
}
}
/* Loading Animation */
.loading {
animation: pulse 1.5s infinite;
}
/* Custom Scrollbar for sidebar */
.sidebar::-webkit-scrollbar {
width: 8px;
}
.sidebar::-webkit-scrollbar-track {
background: rgba(255,255,255,0.1);
border-radius: 4px;
}
.sidebar::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.3);
border-radius: 4px;
}
.sidebar::-webkit-scrollbar-thumb:hover {
background: rgba(255,255,255,0.4);
}
</style>
</head>
<body>
<div class="container">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<i class="fas fa-cloud-sun"></i>
<h1 class="text-xl font-bold">Weather Dashboard</h1>
</div>
<form method="POST" action="/">
<div class="input-group">
<label for="lat">Latitude</label>
<input type="number" step="any" id="lat" name="lat" class="input-field" value="{{ lat }}" placeholder="Enter latitude..." />
</div>
<div class="input-group">
<label for="lon">Longitude</label>
<input type="number" step="any" id="lon" name="lon" class="input-field" value="{{ lon }}" placeholder="Enter longitude..." />
</div>
<button type="submit" class="search-button">
<i class="fas fa-search"></i>
Get Weather
</button>
</form>
</aside>
<!-- Main Content -->
<main class="main-content">
<!-- Header: Current Weather & Location -->
<section class="header-card">
<div>
<div class="header-location">
<i class="fas fa-map-marker-alt"></i>
{{ location_address }}
</div>
<div class="timestamp">
<i class="far fa-clock"></i>
{{ current_time }}
<span class="text-sm opacity-75">{{ timezone }}</span>
</div>
</div>
<div class="flex items-center gap-4">
<div class="current-temp">
{{ current_temp }}°C
{% if current_icon == "☀️" %}
<i class="fas fa-sun weather-icon"></i>
{% elif current_icon == "⛅" %}
<i class="fas fa-cloud-sun weather-icon"></i>
{% elif current_icon == "🌫️" %}
<i class="fas fa-smog weather-icon"></i>
{% elif current_icon == "🌦️" %}
<i class="fas fa-cloud-showers-heavy weather-icon"></i>
{% elif current_icon == "🌧️" %}
<i class="fas fa-cloud-rain weather-icon"></i>
{% elif current_icon == "❄️" %}
<i class="fas fa-snowflake weather-icon"></i>
{% elif current_icon == "⛈️" %}
<i class="fas fa-bolt weather-icon"></i>
{% else %}
<i class="fas fa-question weather-icon"></i>
{% endif %}
</div>
<div class="text-right">
<div class="text-lg font-medium text-green-700">{{ current_desc }}</div>
<div class="weather-details">
<div class="weather-detail">
<i class="fas fa-temperature-low"></i>
Feels like: {{ feels_like }}°C
</div>
<div class="weather-detail">
<i class="fas fa-wind"></i>
{{ current_wind_speed }} km/h
</div>
</div>
</div>
</div>
</section>
<!-- Alert Notification (if alerts were sent) -->
{% if alerts_sent %}
<div class="alert-notification bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 mb-4" role="alert">
<p class="font-bold"><i class="fas fa-exclamation-triangle"></i> Weather Alert Sent</p>
<p>Alerts for upcoming hazardous weather conditions Near Your Farm have been sent to your WhatsApp.</p>
</div>
{% endif %}
<!-- Today's Highlights -->
<section>
<h2 class="section-title">
<i class="fas fa-chart-line"></i>
Today's Highlights
</h2>
<div class="highlights">
<!-- Humidity -->
<div class="highlight-card">
<i class="fas fa-tint highlight-icon"></i>
<div class="highlight-title">Humidity</div>
<div class="highlight-value">{{ today_highlights.humidity }}%</div>
</div>
<!-- Wind Speed -->
<div class="highlight-card">
<i class="fas fa-wind highlight-icon"></i>
<div class="highlight-title">Wind Speed</div>
<div class="highlight-value">{{ today_highlights.windspeed }} km/h</div>
</div>
<!-- UV Index -->
<div class="highlight-card">
<i class="fas fa-sun highlight-icon"></i>
<div class="highlight-title">UV Index</div>
<div class="highlight-value">{{ today_highlights.uv_index }}</div>
</div>
<!-- Pressure -->
<div class="highlight-card">
<i class="fas fa-compress-alt highlight-icon"></i>
<div class="highlight-title">Pressure</div>
<div class="highlight-value">{{ today_highlights.pressure|round(1) }} hPa</div>
</div>
<!-- Cloud Cover -->
<div class="highlight-card">
<i class="fas fa-cloud highlight-icon"></i>
<div class="highlight-title">Cloud Cover</div>
<div class="highlight-value">{{ today_highlights.clouds }}%</div>
</div>
<!-- Precipitation -->
<div class="highlight-card">
<i class="fas fa-cloud-rain highlight-icon"></i>
<div class="highlight-title">Precipitation</div>
<div class="highlight-value">{{ today_highlights.precipitation|round(2) }} mm</div>
</div>
<!-- Soil Moisture -->
<div class="highlight-card">
<i class="fas fa-water highlight-icon"></i>
<div class="highlight-title">Soil Moisture</div>
<div class="highlight-value">{{ today_highlights.soil_moisture|round(3) }} m³/m³</div>
</div>
<!-- Sun Timing -->
<div class="highlight-card">
<div class="flex justify-between items-center">
<div class="text-center flex-1">
<i class="fas fa-sunrise highlight-icon"></i>
<div class="highlight-title">Sunrise</div>
<div class="highlight-value text-lg">{{ today_highlights.sunrise }}</div>
</div>
<div class="border-r border-gray-200 h-16 mx-4"></div>
<div class="text-center flex-1">
<i class="fas fa-sunset highlight-icon"></i>
<div class="highlight-title">Sunset</div>
<div class="highlight-value text-lg">{{ today_highlights.sunset }}</div>
</div>
</div>
</div>
</div>
</section>
<!-- 14-Day Forecast -->
<section class="forecast-section">
<h2 class="section-title">
<i class="fas fa-calendar-alt"></i>
14-Day Forecast
</h2>
<div class="forecast-grid">
{% for day in forecast_list %}
<div class="forecast-card">
<div class="forecast-day">{{ day.day_name }}</div>
<div class="forecast-date">{{ day.date_str }}</div>
<!-- Conditional Forecast Icon -->
{% if day.icon == "☀️" %}
<i class="fas fa-sun forecast-icon"></i>
{% elif day.icon == "⛅" %}
<i class="fas fa-cloud-sun forecast-icon"></i>
{% elif day.icon == "🌫️" %}
<i class="fas fa-smog forecast-icon"></i>
{% elif day.icon == "🌦️" %}
<i class="fas fa-cloud-showers-heavy forecast-icon"></i>
{% elif day.icon == "🌧️" %}
<i class="fas fa-cloud-rain forecast-icon"></i>
{% elif day.icon == "❄️" %}
<i class="fas fa-snowflake forecast-icon"></i>
{% elif day.icon == "⛈️" %}
<i class="fas fa-bolt forecast-icon"></i>
{% else %}
<i class="fas fa-question forecast-icon"></i>
{% endif %}
<div class="text-sm text-green-700 font-medium">{{ day.desc }}</div>
<div class="forecast-temp">{{ day.avg_temp }}°C</div>
<div class="temp-range">
<span><i class="fas fa-arrow-down text-blue-500"></i> {{ day.tmin }}°C</span>
<span><i class="fas fa-arrow-up text-red-500"></i> {{ day.tmax }}°C</span>
</div>
<div class="mt-3">
<div class="text-sm font-medium mb-1">Day Temperatures</div>
<div class="text-sm">Morning: {{ day.morning_temp }}°C</div>
<div class="text-sm">Evening: {{ day.evening_temp }}°C</div>
</div>
<div class="sunrise-sunset">
<div class="flex items-center justify-center gap-1 mb-1">
<i class="fas fa-sun text-yellow-500"></i>
{{ day.sunrise }}
</div>
<div class="flex items-center justify-center gap-1">
<i class="fas fa-moon text-gray-600"></i>
{{ day.sunset }}
</div>
</div>
</div>
{% endfor %}
</div>
</section>
<!-- Footer -->
<footer class="footer-note">
<p>Last updated: {{ current_time }}</p>
</footer>
</main>
</div>
<!-- Scripts -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
// Initialize interactive behaviors
$(document).ready(function() {
// Add loading animation on form submission
$('form').on('submit', function() {
$('.main-content').addClass('loading');
});
// Animate cards on scroll using IntersectionObserver
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}
});
}, observerOptions);
document.querySelectorAll('.highlight-card, .forecast-card').forEach(card => {
card.style.opacity = '0';
card.style.transform = 'translateY(20px)';
observer.observe(card);
});
// Real-time input validation for latitude and longitude
const validateInput = (input) => {
const value = parseFloat(input.value);
if (input.id === 'lat') {
if (value < -90 || value > 90) {
input.setCustomValidity('Latitude must be between -90 and 90 degrees');
} else {
input.setCustomValidity('');
}
} else if (input.id === 'lon') {
if (value < -180 || value > 180) {
input.setCustomValidity('Longitude must be between -180 and 180 degrees');
} else {
input.setCustomValidity('');
}
}
};
document.querySelectorAll('.input-field').forEach(input => {
input.addEventListener('input', () => validateInput(input));
});
});
</script>
</body>
</html>