5 Commits

Author SHA1 Message Date
ef4ffbbf5d Fix weather icons, forecast temps, and update docs
- Replace emoji weather icons with OWM icon images to fix rendering
  issues on Linux displays (e.g.  showing as a rectangle)
- Fix forecast daily max/min to use true high/low across all 3-hour
  slots instead of just the first entry of the day
- Update README: correct BACKGROUND interval (60 min), update kiosk
  setup for labwc/Wayland, add restart-calendar.sh instructions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 21:56:27 +13:00
abf1036864 Fix background photo rotation interval from 1 min to 60 min
BACKGROUND interval was set to 60000ms (1 minute) instead of
3600000ms (60 minutes) as intended.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 21:39:30 +13:00
4b33ac4bde Generalize code and improve documentation
Remove personal information and improve setup documentation:

**Code Generalization:**
- Updated .env.example with placeholder values
- Removed personal API keys and calendar IDs
- Removed all personal background images (15 images deleted)
- Added backgrounds directory README with usage instructions
- Updated .gitignore to exclude background images

**Documentation Improvements:**
- Added comprehensive Weather API setup instructions
  - How to get OpenWeatherMap API key
  - How to find location coordinates using LatLong.net
- Added detailed Google Calendar setup instructions
  - Public iCal feed method (easiest, no auth required)
  - Alternative Google Calendar API method
- Added background image specifications
  - Supported formats, recommended resolution
  - Where to add images and how to change rotation interval
- Added configuration section explaining all settings
  - Environment variables documentation
  - JavaScript intervals configuration
  - How to change image rotation time in both .env and app.js
- Added dynamic text color feature to feature list

**Setup Instructions Now Include:**
- Step-by-step Weather API key acquisition
- Google Calendar public iCal URL setup
- Background image guidelines and rotation configuration
- All configuration options clearly documented

Users can now easily set up their own calendar display without any personal information in the codebase.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 22:36:17 +13:00
ced41148ef Add dynamic text color adjustment based on background brightness
Automatically adjusts text color for optimal readability:
- Bright backgrounds: Text switches to dark color (#1a1a1a)
- Dark backgrounds: Text remains white (#ffffff)
- Uses luminance formula (0.299×R + 0.587×G + 0.114×B) for accurate brightness detection
- Analyzes images using canvas to sample 100x100 pixels
- Smooth color transitions (0.5s) between background changes
- Adjusts text shadows for better contrast in each mode
- Event time color adapts (#0066cc for light, #4a9eff for dark)

Technical implementation:
- Added calculateImageBrightness() function in app.js
- Modified updateBackground() to analyze and apply appropriate CSS class
- Added .light-bg and .dark-bg CSS classes for text color themes
- Brightness threshold set at 140 (out of 255)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 22:25:51 +13:00
11adc10c34 Increase font sizes and make text bolder for better readability
- Increased time display from 3rem to 4.5rem with font-weight 600
- Increased date from 1.1rem to 1.6rem with font-weight 500
- Increased current temperature from 2rem to 3rem with font-weight 600
- Increased weather labels from 0.85rem to 1.2rem with font-weight 600
- Increased weather icons from 1.8rem to 2.5rem
- Increased forecast icons from 1.5rem to 2.2rem
- Increased forecast temps from 0.9rem to 1.3rem with high temps at font-weight 700
- Increased section headers from 1.3rem to 2rem with font-weight 700
- Increased day names from 1rem to 1.5rem with font-weight 700
- Increased day dates from 0.85rem to 1.2rem with font-weight 600
- Increased event times from 0.75rem to 1.1rem with font-weight 700
- Increased event titles from 0.9rem to 1.3rem with font-weight 600
- Increased event locations from 0.85rem to 1.1rem with font-weight 500
- Increased joke text from 0.9rem to 1.3rem with font-weight 500
- Added new background images
- Removed old background images

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 22:22:42 +13:00
10 changed files with 338 additions and 82 deletions

View File

@@ -1,20 +1,25 @@
# Flask Configuration # Flask Configuration
FLASK_SECRET_KEY=your_secret_key_here FLASK_SECRET_KEY=calendar-display-secret-key-change-this
# OpenWeatherMap API # OpenWeatherMap API
OPENWEATHER_API_KEY=your_api_key_here # Get your free API key from: https://openweathermap.org/api
OPENWEATHER_API_KEY=your_openweather_api_key_here
# Weather Location (Hamilton, New Zealand) # Weather Location
WEATHER_LOCATION=Hamilton,NZ # Find coordinates at: https://www.latlong.net/
WEATHER_LAT=-37.7870 WEATHER_LOCATION=YourCity,CountryCode
WEATHER_LON=175.2793 WEATHER_LAT=0.0000
WEATHER_LON=0.0000
# Google Calendar # Google Calendar
# Option 1: Use public iCal URL (easiest - no authentication needed)
# Get from: Google Calendar Settings > Calendar > Integrate Calendar > Public URL to this calendar (iCal format)
GOOGLE_CALENDAR_ID=your_calendar_id@group.calendar.google.com GOOGLE_CALENDAR_ID=your_calendar_id@group.calendar.google.com
GOOGLE_CALENDAR_ICAL_URL=https://calendar.google.com/calendar/ical/your_calendar_id%40group.calendar.google.com/public/basic.ics
# Update Intervals (seconds) # Update Intervals (seconds)
IMAGE_ROTATION_INTERVAL=300 IMAGE_ROTATION_INTERVAL=60
WEATHER_UPDATE_INTERVAL=900 WEATHER_UPDATE_INTERVAL=900
CALENDAR_UPDATE_INTERVAL=300 CALENDAR_UPDATE_INTERVAL=300
JOKE_UPDATE_INTERVAL=3600 JOKE_UPDATE_INTERVAL=3600
CALENDAR_DAYS_AHEAD=7 CALENDAR_DAYS_AHEAD=5

5
.gitignore vendored
View File

@@ -43,3 +43,8 @@ Thumbs.db
# Logs # Logs
*.log *.log
# Background Images (users should add their own)
static/backgrounds/*
!static/backgrounds/README.md
!static/backgrounds/.gitkeep

152
README.md
View File

@@ -7,9 +7,10 @@ A smart display application for Raspberry Pi that shows time, weather, calendar
## Features ## Features
- 🕐 **Real-time Clock** - Current time and date display - 🕐 **Real-time Clock** - Current time and date display
- 🌤️ **Weather** - Current weather and 3-day forecast for Hamilton, New Zealand (OpenWeatherMap) - 🌤️ **Weather** - Current weather and 3-day forecast (OpenWeatherMap)
- 📅 **Google Calendar** - Family calendar events display - 📅 **Google Calendar** - Calendar events display (supports public iCal feeds)
- 🖼️ **Rotating Backgrounds** - Beautiful images from local directory - 🖼️ **Rotating Backgrounds** - Beautiful images from local directory
- 🎨 **Dynamic Text Color** - Automatically adjusts text color based on background brightness
- 😄 **Dad Jokes** - Random jokes to brighten your day (optional) - 😄 **Dad Jokes** - Random jokes to brighten your day (optional)
## Requirements ## Requirements
@@ -55,26 +56,74 @@ GOOGLE_CALENDAR_ID=your_calendar_id@group.calendar.google.com
FLASK_SECRET_KEY=your_random_secret_key FLASK_SECRET_KEY=your_random_secret_key
``` ```
### 4. Set Up Google Calendar API ### 4. Get OpenWeatherMap API Key
1. Go to [OpenWeatherMap](https://openweathermap.org/api)
2. Sign up for a free account
3. Navigate to "API keys" section
4. Copy your API key
5. Add it to your `.env` file as `OPENWEATHER_API_KEY`
**Find Your Location Coordinates:**
1. Go to [LatLong.net](https://www.latlong.net/)
2. Search for your city
3. Copy the latitude and longitude values
4. Add them to your `.env` file as `WEATHER_LAT` and `WEATHER_LON`
### 5. Set Up Google Calendar
**Option 1: Public iCal Feed (Easiest - No Authentication)**
1. Open [Google Calendar](https://calendar.google.com/)
2. Go to Settings (⚙️ gear icon)
3. Select the calendar you want to display
4. Scroll down to "Integrate calendar"
5. Copy the **"Public URL to this calendar"** in iCal format
- The URL looks like: `https://calendar.google.com/calendar/ical/...@group.calendar.google.com/public/basic.ics`
6. Add this URL to your `.env` file as `GOOGLE_CALENDAR_ICAL_URL`
7. Also copy the Calendar ID (looks like `abc123...@group.calendar.google.com`)
8. Add it to your `.env` file as `GOOGLE_CALENDAR_ID`
**Note:** Your calendar must be set to "Public" for this method to work.
**Option 2: Google Calendar API with Credentials (Advanced)**
If you prefer to use private calendars with authentication:
1. Go to [Google Cloud Console](https://console.cloud.google.com/) 1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select existing one 2. Create a new project or select existing one
3. Enable the Google Calendar API 3. Enable the Google Calendar API
4. Create credentials (OAuth 2.0 or Service Account) 4. Create credentials (Service Account)
5. Download the credentials JSON file 5. Download the credentials JSON file
6. Save it as [credentials/google_calendar_credentials.json](credentials/google_calendar_credentials.json) 6. Save it as `credentials/google_calendar_credentials.json`
7. Share your calendar with the service account email
### 5. Add Background Images ### 6. Add Background Images
Place your images in the [static/backgrounds/](static/backgrounds/) directory: Place your images in the `static/backgrounds/` directory:
```bash ```bash
cp /path/to/your/images/*.jpg static/backgrounds/ cp /path/to/your/images/*.jpg static/backgrounds/
``` ```
Supported formats: JPG, PNG, GIF, WebP **Supported formats:** JPG, JPEG, PNG, GIF, WebP
### 6. Run the Application **Recommended specifications:**
- Resolution: 1920x1080 or higher
- Aspect ratio: 16:9 (for full-screen displays)
- File size: Keep under 5MB for faster loading
**Changing Image Rotation Interval:**
Edit `static/js/app.js`:
```javascript
const INTERVALS = {
BACKGROUND: 3600000, // Milliseconds (3600000 = 60 minutes)
...
};
```
### 7. Run the Application
```bash ```bash
python app.py python app.py
@@ -120,27 +169,21 @@ sudo systemctl start calendar-display.service
### Configure Chromium Kiosk Mode ### Configure Chromium Kiosk Mode
Edit the autostart file: The Pi uses **labwc** (Wayland). Create the autostart entry:
```bash ```bash
mkdir -p ~/.config/lxsession/LXDE-pi mkdir -p ~/.config/autostart
nano ~/.config/lxsession/LXDE-pi/autostart nano ~/.config/autostart/chromium-kiosk.desktop
``` ```
Add these lines: Add the following:
```bash ```ini
@xset s off [Desktop Entry]
@xset -dpms Type=Application
@xset s noblank Name=Chromium Kiosk
@chromium-browser --kiosk --noerrdialogs --disable-infobars --disable-session-crashed-bubble http://localhost:5000 Exec=chromium-browser --ozone-platform=wayland --kiosk --noerrdialogs --incognito --disable-infobars --simulate-touch-screen --force-show-cursor http://localhost:5002
``` X-GNOME-Autostart-enabled=true
Disable screensaver and cursor (optional):
```bash
sudo apt-get install unclutter
echo "@unclutter -idle 0" >> ~/.config/lxsession/LXDE-pi/autostart
``` ```
### Reboot ### Reboot
@@ -151,15 +194,62 @@ sudo reboot
The display should now start automatically on boot! The display should now start automatically on boot!
### Restarting Without Rebooting
A helper script is included to restart both the Flask service and the Chromium kiosk without rebooting:
```bash
bash ~/restart-calendar.sh
```
This will:
1. Restart the `calendar-display` systemd service
2. Kill and relaunch Chromium in the correct Wayland session
## Configuration ## Configuration
Edit [.env](.env) to customize: ### Environment Variables (`.env` file)
- `IMAGE_ROTATION_INTERVAL` - Seconds between background changes (default: 300) **Weather Settings:**
- `WEATHER_UPDATE_INTERVAL` - Seconds between weather updates (default: 900) - `OPENWEATHER_API_KEY` - Your OpenWeatherMap API key
- `CALENDAR_UPDATE_INTERVAL` - Seconds between calendar updates (default: 300) - `WEATHER_LOCATION` - City name and country code (e.g., "London,UK")
- `JOKE_UPDATE_INTERVAL` - Seconds between joke updates (default: 3600) - `WEATHER_LAT` - Latitude of your location
- `CALENDAR_DAYS_AHEAD` - Number of days ahead to show events (default: 7) - `WEATHER_LON` - Longitude of your location
**Calendar Settings:**
- `GOOGLE_CALENDAR_ID` - Your calendar ID
- `GOOGLE_CALENDAR_ICAL_URL` - Public iCal feed URL (easiest method)
- `CALENDAR_DAYS_AHEAD` - Number of days ahead to show events (default: 5)
**Update Intervals (in seconds):**
- `WEATHER_UPDATE_INTERVAL` - Weather refresh interval (default: 900)
- `CALENDAR_UPDATE_INTERVAL` - Calendar refresh interval (default: 300)
- `JOKE_UPDATE_INTERVAL` - Dad joke refresh interval (default: 3600)
**Other:**
- `FLASK_SECRET_KEY` - Flask session secret key
### JavaScript Configuration (`static/js/app.js`)
For more precise control over update intervals, edit the `INTERVALS` object:
```javascript
const INTERVALS = {
TIME: 1000, // 1 second
WEATHER: 900000, // 15 minutes
CALENDAR: 300000, // 5 minutes
BACKGROUND: 3600000, // 60 minutes
JOKE: 3600000 // 1 hour
};
```
**To change image rotation time:**
1. Open `static/js/app.js`
2. Find the `INTERVALS` object at the top
3. Change `BACKGROUND` to your desired value in milliseconds
- 300000 = 5 minutes
- 1800000 = 30 minutes
- 3600000 = 60 minutes (default)
## Project Structure ## Project Structure

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

View File

@@ -0,0 +1,48 @@
# Background Images
This directory is where you place your background images for the calendar display.
## Adding Images
1. Copy your image files to this directory:
```bash
cp /path/to/your/images/* static/backgrounds/
```
2. Supported formats:
- JPEG/JPG
- PNG
- GIF
- WebP
3. Recommended image specifications:
- Resolution: 1920x1080 or higher
- Aspect ratio: 16:9 (for full-screen displays)
- File size: Keep under 5MB for faster loading
## Image Rotation
Images automatically rotate at the interval specified in your `.env` file.
To change the rotation interval, edit the `.env` file:
```bash
# Time in seconds (default: 60 seconds = 1 minute)
IMAGE_ROTATION_INTERVAL=60
```
Or directly in `static/js/app.js`:
```javascript
const INTERVALS = {
BACKGROUND: 60000, // Milliseconds (60000 = 1 minute)
...
};
```
## Tips
- Use high-quality images for best display
- The app will randomly select images from this directory
- Delete or move images you no longer want displayed
- Images are cached by the browser, so you may need to hard-refresh (Ctrl+F5) to see new images immediately

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

View File

@@ -11,6 +11,48 @@ body {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
color: #ffffff; color: #ffffff;
transition: color 0.5s ease-in-out;
}
/* Dark text for bright backgrounds */
body.light-bg {
color: #1a1a1a;
}
body.light-bg .time,
body.light-bg .date,
body.light-bg .current-temp,
body.light-bg .weather-label,
body.light-bg .weather-icon,
body.light-bg .forecast-icon,
body.light-bg section h2,
body.light-bg .day-name,
body.light-bg .day-date,
body.light-bg .event-title,
body.light-bg .event-location,
body.light-bg .joke {
text-shadow: 1px 1px 3px rgba(255, 255, 255, 0.8);
}
body.light-bg .event-time {
color: #0066cc;
}
body.light-bg .event {
border-left-color: #0066cc;
}
/* White text for dark backgrounds (default) */
body.dark-bg {
color: #ffffff;
}
body.dark-bg .event-time {
color: #4a9eff;
}
body.dark-bg .event {
border-left-color: #4a9eff;
} }
/* Background */ /* Background */
@@ -66,15 +108,15 @@ body {
} }
.time { .time {
font-size: 3rem; font-size: 4.5rem;
font-weight: 300; font-weight: 600;
line-height: 1; line-height: 1;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
} }
.date { .date {
font-size: 1.1rem; font-size: 1.6rem;
font-weight: 300; font-weight: 500;
margin-top: 0.3rem; margin-top: 0.3rem;
opacity: 0.9; opacity: 0.9;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
@@ -94,20 +136,21 @@ body {
} }
.weather-label { .weather-label {
font-size: 0.85rem; font-size: 1.2rem;
font-weight: 600;
opacity: 0.8; opacity: 0.8;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
} }
.current-temp { .current-temp {
font-size: 2rem; font-size: 3rem;
font-weight: 300; font-weight: 600;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
} }
.weather-icon { .weather-icon {
font-size: 1.8rem; font-size: 2.5rem;
} }
.header-forecast { .header-forecast {
@@ -123,17 +166,17 @@ body {
} }
.forecast-icon { .forecast-icon {
font-size: 1.5rem; font-size: 2.2rem;
} }
.forecast-temps { .forecast-temps {
display: flex; display: flex;
gap: 0.3rem; gap: 0.3rem;
font-size: 0.9rem; font-size: 1.3rem;
} }
.forecast-temps .high { .forecast-temps .high {
font-weight: 500; font-weight: 700;
} }
.forecast-temps .low { .forecast-temps .low {
@@ -157,8 +200,8 @@ section {
} }
section h2 { section h2 {
font-size: 1.3rem; font-size: 2rem;
font-weight: 400; font-weight: 700;
margin-bottom: 0.8rem; margin-bottom: 0.8rem;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
} }
@@ -264,13 +307,14 @@ section h3 {
} }
.day-name { .day-name {
font-size: 1rem; font-size: 1.5rem;
font-weight: 500; font-weight: 700;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
} }
.day-date { .day-date {
font-size: 0.85rem; font-size: 1.2rem;
font-weight: 600;
opacity: 0.8; opacity: 0.8;
margin-top: 0.2rem; margin-top: 0.2rem;
} }
@@ -284,7 +328,8 @@ section h3 {
} }
.no-events { .no-events {
font-size: 0.85rem; font-size: 1.2rem;
font-weight: 500;
opacity: 0.5; opacity: 0.5;
font-style: italic; font-style: italic;
padding: 0.5rem; padding: 0.5rem;
@@ -299,20 +344,22 @@ section h3 {
} }
.event-time { .event-time {
font-size: 0.75rem; font-size: 1.1rem;
font-weight: 500; font-weight: 700;
margin-bottom: 0.2rem; margin-bottom: 0.2rem;
color: #4a9eff; color: #4a9eff;
} }
.event-title { .event-title {
font-size: 0.9rem; font-size: 1.3rem;
font-weight: 600;
margin-bottom: 0.2rem; margin-bottom: 0.2rem;
line-height: 1.2; line-height: 1.2;
} }
.event-location { .event-location {
font-size: 0.85rem; font-size: 1.1rem;
font-weight: 500;
opacity: 0.7; opacity: 0.7;
font-style: italic; font-style: italic;
} }
@@ -333,7 +380,8 @@ section h3 {
} }
.joke { .joke {
font-size: 0.9rem; font-size: 1.3rem;
font-weight: 500;
font-style: italic; font-style: italic;
text-align: center; text-align: center;
opacity: 0.9; opacity: 0.9;
@@ -362,19 +410,19 @@ section h3 {
/* Responsive adjustments for smaller screens */ /* Responsive adjustments for smaller screens */
@media (max-width: 1200px) { @media (max-width: 1200px) {
.time { .time {
font-size: 4rem; font-size: 5rem;
} }
.date { .date {
font-size: 1.5rem; font-size: 2rem;
} }
.current-temp { .current-temp {
font-size: 3rem; font-size: 4rem;
} }
section h2 { section h2 {
font-size: 1.6rem; font-size: 2.5rem;
} }
} }
@@ -384,10 +432,10 @@ section h3 {
} }
.time { .time {
font-size: 3rem; font-size: 4rem;
} }
.current-temp { .current-temp {
font-size: 2.5rem; font-size: 3.5rem;
} }
} }

View File

@@ -3,22 +3,15 @@ const INTERVALS = {
TIME: 1000, // 1 second TIME: 1000, // 1 second
WEATHER: 900000, // 15 minutes WEATHER: 900000, // 15 minutes
CALENDAR: 300000, // 5 minutes CALENDAR: 300000, // 5 minutes
BACKGROUND: 60000, // 1 minute BACKGROUND: 3600000, // 60 minutes
JOKE: 3600000 // 1 hour JOKE: 3600000 // 1 hour
}; };
// Weather icon mapping (OpenWeatherMap icons to emoji or text) // Return an OWM icon image tag for a given icon code
const WEATHER_ICONS = { function weatherIcon(code) {
'01d': '☀️', '01n': '🌙', const src = `https://openweathermap.org/img/wn/${code}@2x.png`;
'02d': '⛅', '02n': '☁️', return `<img src="${src}" alt="${code}" class="owm-icon">`;
'03d': '☁️', '03n': '☁️', }
'04d': '☁️', '04n': '☁️',
'09d': '🌧️', '09n': '🌧️',
'10d': '🌦️', '10n': '🌧️',
'11d': '⛈️', '11n': '⛈️',
'13d': '❄️', '13n': '❄️',
'50d': '🌫️', '50n': '🌫️'
};
// Initialize the application // Initialize the application
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
@@ -69,7 +62,7 @@ async function updateWeather() {
const { current, forecast } = data; const { current, forecast } = data;
document.getElementById('current-temp').textContent = `${current.temp}°`; document.getElementById('current-temp').textContent = `${current.temp}°`;
document.getElementById('weather-icon').textContent = WEATHER_ICONS[current.icon] || '🌤️'; document.getElementById('weather-icon').innerHTML = weatherIcon(current.icon);
// Update 3-day forecast in header // Update 3-day forecast in header
const headerForecast = document.getElementById('header-forecast'); const headerForecast = document.getElementById('header-forecast');
@@ -80,7 +73,7 @@ async function updateWeather() {
dayElement.className = 'forecast-item'; dayElement.className = 'forecast-item';
dayElement.innerHTML = ` dayElement.innerHTML = `
<div class="weather-label">${day.date}</div> <div class="weather-label">${day.date}</div>
<div class="forecast-icon">${WEATHER_ICONS[day.icon] || '🌤️'}</div> <div class="forecast-icon">${weatherIcon(day.icon)}</div>
<div class="forecast-temps"> <div class="forecast-temps">
<span class="high">${day.temp_max}°</span> <span class="high">${day.temp_max}°</span>
<span class="low">${day.temp_min}°</span> <span class="low">${day.temp_min}°</span>
@@ -212,6 +205,56 @@ async function updateCalendar() {
} }
} }
// Calculate brightness of an image
function calculateImageBrightness(imageSrc, callback) {
const img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = () => {
// Create a canvas to analyze the image
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Use a smaller size for faster processing
canvas.width = 100;
canvas.height = 100;
// Draw the image scaled down
ctx.drawImage(img, 0, 0, 100, 100);
// Get image data
const imageData = ctx.getImageData(0, 0, 100, 100);
const data = imageData.data;
// Calculate average brightness
let totalBrightness = 0;
const pixelCount = data.length / 4;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Calculate perceived brightness (using luminance formula)
const brightness = (0.299 * r + 0.587 * g + 0.114 * b);
totalBrightness += brightness;
}
const avgBrightness = totalBrightness / pixelCount;
// Return brightness value (0-255)
callback(avgBrightness);
};
img.onerror = () => {
console.error('Error loading image for brightness analysis');
// Default to dark background if error
callback(100);
};
img.src = imageSrc;
}
// Update background image // Update background image
async function updateBackground() { async function updateBackground() {
try { try {
@@ -226,11 +269,28 @@ async function updateBackground() {
const img = new Image(); const img = new Image();
img.onload = () => { img.onload = () => {
backgroundElement.style.backgroundImage = `url('${data.image}')`; backgroundElement.style.backgroundImage = `url('${data.image}')`;
// Analyze brightness and adjust text color
calculateImageBrightness(data.image, (brightness) => {
// Threshold: 128 is middle brightness
// If brightness > 140, use dark text (light background)
// If brightness <= 140, use white text (dark background)
if (brightness > 140) {
document.body.classList.remove('dark-bg');
document.body.classList.add('light-bg');
} else {
document.body.classList.remove('light-bg');
document.body.classList.add('dark-bg');
}
});
}; };
img.src = data.image; img.src = data.image;
} else if (data.color) { } else if (data.color) {
backgroundElement.style.backgroundColor = data.color; backgroundElement.style.backgroundColor = data.color;
backgroundElement.style.backgroundImage = 'none'; backgroundElement.style.backgroundImage = 'none';
// Assume solid colors are dark
document.body.classList.remove('light-bg');
document.body.classList.add('dark-bg');
} }
} catch (error) { } catch (error) {