Refine calendar display layout and features
Major improvements to the calendar display application: - Redesigned weather display: moved forecast to header alongside time - Added 3-day forecast with icons and high/low temps in header - Simplified main layout: removed separate weather section, calendar now full-width - Made layout more compact to fit on one screen without scrolling - Added 2 new background images for rotation - Updated image rotation interval to 1 minute - Improved responsive design and spacing The display now shows: - Header: Time/date on left, current weather + 3-day forecast on right - Main: Full-width calendar events section - Footer: Dad joke All elements now fit on a single screen with a clean, readable layout. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ A smart display application for Raspberry Pi that shows time, weather, calendar
|
||||
## Features
|
||||
|
||||
- 🕐 **Real-time Clock** - Current time and date display
|
||||
- 🌤️ **Weather** - Current weather and 5-day forecast for Hamilton, New Zealand (OpenWeatherMap)
|
||||
- 🌤️ **Weather** - Current weather and 3-day forecast for Hamilton, New Zealand (OpenWeatherMap)
|
||||
- 📅 **Google Calendar** - Family calendar events display
|
||||
- 🖼️ **Rotating Backgrounds** - Beautiful images from local directory
|
||||
- 😄 **Dad Jokes** - Random jokes to brighten your day (optional)
|
||||
|
||||
6
app.py
6
app.py
@@ -44,7 +44,7 @@ def get_weather():
|
||||
current_response.raise_for_status()
|
||||
current_data = current_response.json()
|
||||
|
||||
# Fetch 5-day forecast
|
||||
# Fetch 3-day forecast
|
||||
forecast_url = f"https://api.openweathermap.org/data/2.5/forecast"
|
||||
forecast_response = requests.get(forecast_url, params=params, timeout=10)
|
||||
forecast_response.raise_for_status()
|
||||
@@ -54,9 +54,9 @@ def get_weather():
|
||||
daily_forecast = []
|
||||
seen_dates = set()
|
||||
|
||||
for item in forecast_data['list'][:40]: # Next 5 days (8 forecasts per day)
|
||||
for item in forecast_data['list'][:40]: # Next 3 days (8 forecasts per day)
|
||||
date = datetime.fromtimestamp(item['dt']).date()
|
||||
if date not in seen_dates and len(daily_forecast) < 5:
|
||||
if date not in seen_dates and len(daily_forecast) < 3:
|
||||
seen_dates.add(date)
|
||||
daily_forecast.append({
|
||||
'date': date.strftime('%a'),
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
BIN
static/backgrounds/IMG_9554.jpeg
Normal file
BIN
static/backgrounds/IMG_9554.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
@@ -46,7 +46,7 @@ body {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
@@ -54,11 +54,11 @@ body {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 1.5rem 2rem;
|
||||
border-radius: 15px;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.time-display {
|
||||
@@ -67,16 +67,16 @@ body {
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 5rem;
|
||||
font-size: 3rem;
|
||||
font-weight: 300;
|
||||
line-height: 1;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 1.8rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 300;
|
||||
margin-top: 0.5rem;
|
||||
margin-top: 0.3rem;
|
||||
opacity: 0.9;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
@@ -84,25 +84,67 @@ body {
|
||||
.weather-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.current-weather-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.weather-label {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.current-temp {
|
||||
font-size: 4rem;
|
||||
font-size: 2rem;
|
||||
font-weight: 300;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.weather-icon {
|
||||
font-size: 4rem;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.header-forecast {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.forecast-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.forecast-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.forecast-temps {
|
||||
display: flex;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.forecast-temps .high {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.forecast-temps .low {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Main content */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -110,44 +152,45 @@ body {
|
||||
section {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 2rem;
|
||||
border-radius: 15px;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 10px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
section h2 {
|
||||
font-size: 2rem;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 400;
|
||||
margin-bottom: 1.5rem;
|
||||
margin-bottom: 0.8rem;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
section h3 {
|
||||
font-size: 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 400;
|
||||
margin: 1.5rem 0 1rem;
|
||||
margin: 1rem 0 0.5rem;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* Weather Section */
|
||||
.weather-description {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.6rem;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.weather-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
margin: 1.5rem 0;
|
||||
gap: 0.4rem;
|
||||
margin: 0.8rem 0;
|
||||
}
|
||||
|
||||
.weather-detail {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 1.3rem;
|
||||
padding: 0.5rem 0;
|
||||
font-size: 0.95rem;
|
||||
padding: 0.3rem 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
@@ -159,32 +202,32 @@ section h3 {
|
||||
.forecast-days {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.forecast-day {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr 120px;
|
||||
grid-template-columns: 60px 1fr 100px;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
padding: 0.6rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 10px;
|
||||
gap: 1rem;
|
||||
border-radius: 8px;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.forecast-day-name {
|
||||
font-size: 1.3rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.forecast-description {
|
||||
font-size: 1.1rem;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.forecast-temp {
|
||||
font-size: 1.3rem;
|
||||
font-size: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -201,36 +244,36 @@ section h3 {
|
||||
.events-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.event {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 1.2rem;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #4a9eff;
|
||||
padding: 0.8rem;
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid #4a9eff;
|
||||
}
|
||||
|
||||
.event-time {
|
||||
font-size: 1.1rem;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-bottom: 0.3rem;
|
||||
color: #4a9eff;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0.3rem;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.event-location {
|
||||
font-size: 1rem;
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.7;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.event-placeholder {
|
||||
font-size: 1.2rem;
|
||||
font-size: 1rem;
|
||||
opacity: 0.6;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
@@ -238,15 +281,15 @@ section h3 {
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
margin-top: 2rem;
|
||||
margin-top: 1rem;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 1.5rem 2rem;
|
||||
border-radius: 15px;
|
||||
padding: 0.8rem 1.5rem;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.joke {
|
||||
font-size: 1.2rem;
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
opacity: 0.9;
|
||||
|
||||
@@ -3,7 +3,7 @@ const INTERVALS = {
|
||||
TIME: 1000, // 1 second
|
||||
WEATHER: 900000, // 15 minutes
|
||||
CALENDAR: 300000, // 5 minutes
|
||||
BACKGROUND: 300000, // 5 minutes
|
||||
BACKGROUND: 60000, // 1 minute
|
||||
JOKE: 3600000 // 1 hour
|
||||
};
|
||||
|
||||
@@ -65,32 +65,28 @@ async function updateWeather() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update current weather
|
||||
// Update current weather in header
|
||||
const { current, forecast } = data;
|
||||
|
||||
document.getElementById('current-temp').textContent = `${current.temp}°`;
|
||||
document.getElementById('weather-icon').textContent = WEATHER_ICONS[current.icon] || '🌤️';
|
||||
document.getElementById('weather-description').textContent = current.description;
|
||||
document.getElementById('feels-like').textContent = `${current.feels_like}°`;
|
||||
document.getElementById('humidity').textContent = `${current.humidity}%`;
|
||||
document.getElementById('wind-speed').textContent = `${current.wind_speed} km/h`;
|
||||
|
||||
// Update forecast
|
||||
const forecastContainer = document.getElementById('forecast-container');
|
||||
forecastContainer.innerHTML = '';
|
||||
// Update 3-day forecast in header
|
||||
const headerForecast = document.getElementById('header-forecast');
|
||||
headerForecast.innerHTML = '';
|
||||
|
||||
forecast.forEach(day => {
|
||||
const dayElement = document.createElement('div');
|
||||
dayElement.className = 'forecast-day';
|
||||
dayElement.className = 'forecast-item';
|
||||
dayElement.innerHTML = `
|
||||
<div class="forecast-day-name">${day.date}</div>
|
||||
<div class="forecast-description">${day.description}</div>
|
||||
<div class="forecast-temp">
|
||||
<div class="weather-label">${day.date}</div>
|
||||
<div class="forecast-icon">${WEATHER_ICONS[day.icon] || '🌤️'}</div>
|
||||
<div class="forecast-temps">
|
||||
<span class="high">${day.temp_max}°</span>
|
||||
<span class="low">${day.temp_min}°</span>
|
||||
</div>
|
||||
`;
|
||||
forecastContainer.appendChild(dayElement);
|
||||
headerForecast.appendChild(dayElement);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -19,43 +19,19 @@
|
||||
<div id="date" class="date">Loading...</div>
|
||||
</div>
|
||||
<div class="weather-summary">
|
||||
<div id="current-temp" class="current-temp">--°</div>
|
||||
<div id="weather-icon" class="weather-icon"></div>
|
||||
<div class="current-weather-item">
|
||||
<div class="weather-label">Today</div>
|
||||
<div id="current-temp" class="current-temp">--°</div>
|
||||
<div id="weather-icon" class="weather-icon"></div>
|
||||
</div>
|
||||
<div id="header-forecast" class="header-forecast">
|
||||
<!-- 3-day forecast will be inserted here -->
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main content area -->
|
||||
<div class="main-content">
|
||||
<!-- Weather Section -->
|
||||
<section class="weather-section">
|
||||
<h2>Weather</h2>
|
||||
<div class="current-weather">
|
||||
<div id="weather-description" class="weather-description">Loading...</div>
|
||||
<div class="weather-details">
|
||||
<div class="weather-detail">
|
||||
<span class="label">Feels like:</span>
|
||||
<span id="feels-like">--°</span>
|
||||
</div>
|
||||
<div class="weather-detail">
|
||||
<span class="label">Humidity:</span>
|
||||
<span id="humidity">--%</span>
|
||||
</div>
|
||||
<div class="weather-detail">
|
||||
<span class="label">Wind:</span>
|
||||
<span id="wind-speed">-- km/h</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5-Day Forecast -->
|
||||
<div class="forecast">
|
||||
<h3>Forecast</h3>
|
||||
<div id="forecast-container" class="forecast-days">
|
||||
<!-- Forecast items will be inserted here -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Calendar Section -->
|
||||
<section class="calendar-section">
|
||||
<h2>Upcoming Events</h2>
|
||||
|
||||
Reference in New Issue
Block a user