Compare commits
26 Commits
c45cfc9aa0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7aed6e5707 | |||
| 0746ecd3d1 | |||
| 44d5e206ca | |||
| fa9534609c | |||
| 7b9095f849 | |||
| 7b1f0d2be2 | |||
| 17bdc73457 | |||
| 02b53b01a1 | |||
| f41bc910bc | |||
| d2020e57ed | |||
| 084401bb3a | |||
| 0f56d23d44 | |||
| 1f02269e75 | |||
| ce93b8a092 | |||
| e03d50ed5b | |||
| 9498c2dcc2 | |||
| ade17801a0 | |||
| e9d358d1e5 | |||
| 45ae372b8c | |||
| 8ec5a8e49e | |||
| 03469097c8 | |||
| f889c4dc68 | |||
| 9c6c619071 | |||
| 70ab2550ad | |||
| a1bb4a1010 | |||
| a48a06ed04 |
10
index.html
10
index.html
@@ -81,12 +81,18 @@
|
||||
|
||||
<h3>Weather Overlay</h3>
|
||||
<ul>
|
||||
<li><a href="/weather_overlay/index.html">Weather Overlay</a></li>
|
||||
<li><a href="/weather_overlay/weather.html">Weather Overlay</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>System Monitor Overlay</h3>
|
||||
<ul>
|
||||
<li><a href="/system_monitor_overlay/index.html">System Monitor Overlay</a></li>
|
||||
<li><a href="/system_monitor_overlay/system_monitor.html">System Monitor Overlay</a></li>
|
||||
<li><a href="/system_monitor_overlay/system_monitor_no_heading.html">System Monitor Overlay (No Heading)</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>RSS Ticker Overlay</h3>
|
||||
<ul>
|
||||
<li><a href="/rss_ticker_overlay/rss_ticker.html">RSS Ticker Overlay</a></li>
|
||||
</ul>
|
||||
<p>For detailed usage instructions and to understand how to deploy these overlays, please refer to the <a href="https://gitea.ramforth.net/ramforth/Overlays/src/branch/main/azuracast/README.md">Azuracast README.md</a> within the Gitea repository.</p>
|
||||
|
||||
|
||||
22
rss_ticker_overlay/README.md
Normal file
22
rss_ticker_overlay/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# RSS Ticker Overlay
|
||||
|
||||
This overlay displays headlines from an RSS feed as a horizontally scrolling ticker. It's designed to be used as a browser source in streaming software like OBS Studio.
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Add to OBS Studio:**
|
||||
|
||||
* Add a new "Browser" source to your scene.
|
||||
* Set the URL to the `rss_ticker.html` file in this directory.
|
||||
* Set the width and height to your desired dimensions.
|
||||
|
||||
2. **Customize RSS Feed:**
|
||||
|
||||
To set your RSS feed URL, you need to open the `rss_ticker.html` file in a regular web browser (like Firefox or Chrome) first.
|
||||
|
||||
1. Open the `rss_ticker.html` file in your browser.
|
||||
2. Enter the full URL of the RSS feed in the input field and click the "Update RSS" button.
|
||||
3. The ticker will now display headlines from your chosen RSS feed. This URL will be saved in your browser's local storage, so it will be remembered the next time you open the overlay.
|
||||
4. You can now add the `rss_ticker.html` file as a browser source in OBS Studio. It will use the RSS feed you just set.
|
||||
|
||||
**Note:** To reset the RSS feed URL, you can open the browser's developer tools (usually by pressing F12), go to the "Application" or "Storage" tab, find "Local Storage", and delete the `rss_feed_url` key.
|
||||
24
rss_ticker_overlay/cors_proxy.py
Normal file
24
rss_ticker_overlay/cors_proxy.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from flask import Flask, request, Response
|
||||
import requests
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/proxy')
|
||||
def proxy():
|
||||
url = request.args.get('url')
|
||||
if not url:
|
||||
return "Missing URL parameter", 400
|
||||
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
content_type = response.headers.get('Content-Type', 'text/plain')
|
||||
headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': content_type
|
||||
}
|
||||
return Response(response.content, response.status_code, headers)
|
||||
except requests.exceptions.RequestException as e:
|
||||
return str(e), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=8001)
|
||||
151
rss_ticker_overlay/rss_ticker.html
Normal file
151
rss_ticker_overlay/rss_ticker.html
Normal file
@@ -0,0 +1,151 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>RSS Ticker Overlay</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden; /* Hide scrollbar */
|
||||
}
|
||||
#ticker-container {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
#ticker-content {
|
||||
display: inline-block;
|
||||
padding-left: 100%; /* Start off-screen */
|
||||
|
||||
}
|
||||
@keyframes ticker-scroll {
|
||||
0% { transform: translate3d(0, 0, 0); }
|
||||
100% { transform: translate3d(-100%, 0, 0); }
|
||||
}
|
||||
#rss-input-container {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
border: 1px solid white;
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
z-index: 100;
|
||||
}
|
||||
#rss-url-input {
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
width: 70%;
|
||||
}
|
||||
#update-rss-button {
|
||||
padding: 5px 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="ticker-container">
|
||||
<span id="ticker-content">Loading RSS feed...</span>
|
||||
</div>
|
||||
|
||||
<div id="rss-input-container">
|
||||
<input type="text" id="rss-url-input" placeholder="Enter RSS Feed URL">
|
||||
<button id="update-rss-button">Update RSS</button>
|
||||
<button id="reset-rss-button">Reset</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const defaultRssUrl = "https://thecafeterium.com/feed/"; // Default theCafeterium news feed
|
||||
|
||||
function fetchRssFeed(rssUrl) {
|
||||
console.log('Fetching RSS feed:', rssUrl);
|
||||
const corsProxy = "http://localhost:8001/proxy?url=";
|
||||
const url = `${corsProxy}${encodeURIComponent(rssUrl)}`;
|
||||
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
console.log('Proxy response:', response);
|
||||
if (response.ok) return response.text();
|
||||
throw new Error('Network response was not ok.');
|
||||
})
|
||||
.then(xmlText => {
|
||||
console.log('Raw XML from proxy:', xmlText);
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(xmlText, "text/xml");
|
||||
console.log('Parsed XML Doc:', xmlDoc);
|
||||
const items = xmlDoc.querySelectorAll("item");
|
||||
let headlines = [];
|
||||
items.forEach(item => {
|
||||
const titleElement = item.querySelector("title");
|
||||
if (titleElement) {
|
||||
headlines.push(titleElement.textContent);
|
||||
}
|
||||
});
|
||||
if (headlines.length > 0) {
|
||||
const tickerContent = document.getElementById('ticker-content');
|
||||
tickerContent.textContent = headlines.join(" | ");
|
||||
|
||||
// Calculate dynamic animation duration
|
||||
const pixelsPerSecond = 50; // Adjust this value to change the perceived speed
|
||||
const contentWidth = tickerContent.scrollWidth;
|
||||
const animationDuration = contentWidth / pixelsPerSecond;
|
||||
|
||||
// Apply and restart animation
|
||||
tickerContent.style.animationDuration = `${animationDuration}s`;
|
||||
tickerContent.style.animation = 'none'; // Reset animation
|
||||
void tickerContent.offsetWidth; // Trigger reflow
|
||||
tickerContent.style.animation = `ticker-scroll ${animationDuration}s linear infinite`;
|
||||
|
||||
} else {
|
||||
document.getElementById('ticker-content').textContent = "No headlines found in feed.";
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching RSS feed:', error);
|
||||
document.getElementById('ticker-content').textContent = "Error loading RSS feed.";
|
||||
});
|
||||
}
|
||||
|
||||
function saveRssUrl(rssUrl) {
|
||||
localStorage.setItem('rss_feed_url', rssUrl);
|
||||
}
|
||||
|
||||
function loadRssUrl() {
|
||||
return localStorage.getItem('rss_feed_url');
|
||||
}
|
||||
|
||||
document.getElementById('update-rss-button').addEventListener('click', () => {
|
||||
const rssUrl = document.getElementById('rss-url-input').value;
|
||||
if (rssUrl) {
|
||||
saveRssUrl(rssUrl);
|
||||
fetchRssFeed(rssUrl);
|
||||
document.getElementById('rss-input-container').style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('reset-rss-button').addEventListener('click', () => {
|
||||
localStorage.removeItem('rss_feed_url');
|
||||
document.getElementById('rss-input-container').style.display = 'block';
|
||||
document.getElementById('rss-url-input').value = '';
|
||||
document.getElementById('ticker-content').textContent = "Loading RSS feed...";
|
||||
});
|
||||
|
||||
// Initial load
|
||||
const storedRssUrl = loadRssUrl();
|
||||
if (storedRssUrl) {
|
||||
fetchRssFeed(storedRssUrl);
|
||||
document.getElementById('rss-input-container').style.display = 'none';
|
||||
} else {
|
||||
fetchRssFeed(defaultRssUrl);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
68
system_monitor_overlay/README.md
Normal file
68
system_monitor_overlay/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# System Monitor Overlay
|
||||
|
||||
This overlay displays real-time system information like CPU and memory usage. It's designed to be used as a browser source in streaming software like OBS Studio.
|
||||
|
||||
## Example Output
|
||||
|
||||

|
||||
|
||||
## Setting up the Python Environment
|
||||
|
||||
Before you can run the server, it's recommended to create a virtual environment to keep the dependencies for this project isolated from your system's Python installation.
|
||||
|
||||
1. **Create a virtual environment:**
|
||||
|
||||
```bash
|
||||
python3 -m venv venv
|
||||
```
|
||||
|
||||
2. **Activate the virtual environment:**
|
||||
|
||||
* On Linux/macOS (bash/zsh):
|
||||
|
||||
```bash
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
* On Linux/macOS (fish):
|
||||
|
||||
```bash
|
||||
source venv/bin/activate.fish
|
||||
```
|
||||
|
||||
* On Windows:
|
||||
|
||||
```bash
|
||||
.\venv\Scripts\activate
|
||||
```
|
||||
|
||||
You will know the virtual environment is active when you see `(venv)` at the beginning of your command prompt.
|
||||
|
||||
## Installation
|
||||
|
||||
Once you have activated the virtual environment, you can install the required library and run the server.
|
||||
|
||||
1. **Install the required library:**
|
||||
|
||||
```bash
|
||||
pip install psutil
|
||||
```
|
||||
|
||||
2. **Run the server:**
|
||||
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
The server will start on port 8000.
|
||||
|
||||
3. **Add to OBS Studio:**
|
||||
|
||||
* Add a new "Browser" source to your scene.
|
||||
* Set the URL to the `system_monitor.html` file in this directory.
|
||||
* Set the width and height to your desired dimensions.
|
||||
|
||||
## Overlays
|
||||
|
||||
* `system_monitor.html`: The default overlay with a heading.
|
||||
* `system_monitor_no_heading.html`: An alternative overlay without the heading.
|
||||
BIN
system_monitor_overlay/obs_example.png
Normal file
BIN
system_monitor_overlay/obs_example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
@@ -6,6 +6,13 @@ import json
|
||||
PORT = 8000
|
||||
|
||||
class SystemInfoHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def do_OPTIONS(self):
|
||||
self.send_response(200, "ok")
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
|
||||
self.send_header("Access-Control-Allow-Headers", "X-Requested-With, Content-type")
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == '/stats':
|
||||
self.send_response(200)
|
||||
@@ -20,6 +27,16 @@ class SystemInfoHandler(http.server.SimpleHTTPRequestHandler):
|
||||
else:
|
||||
super().do_GET()
|
||||
|
||||
with socketserver.TCPServer(('', PORT), SystemInfoHandler) as httpd:
|
||||
class QuietSystemInfoHandler(SystemInfoHandler):
|
||||
def log_message(self, format, *args):
|
||||
if self.path != '/stats':
|
||||
super().log_message(format, *args)
|
||||
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
with socketserver.TCPServer(('', PORT), QuietSystemInfoHandler) as httpd:
|
||||
print("serving at port", PORT)
|
||||
httpd.serve_forever()
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\nShutting down server.")
|
||||
httpd.shutdown()
|
||||
|
||||
@@ -12,17 +12,27 @@
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.stat {
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.descriptor {
|
||||
font-weight: 300; /* Light */
|
||||
}
|
||||
.value {
|
||||
font-weight: 700; /* Bold */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>System Monitor</h1>
|
||||
<div>
|
||||
<h2>CPU Usage</h2>
|
||||
<p id="cpu-usage"></p>
|
||||
<div class="stat">
|
||||
<span class="descriptor">CPU Usage:</span>
|
||||
<span class="value" id="cpu-usage"></span>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Memory Usage</h2>
|
||||
<p id="memory-usage"></p>
|
||||
<div class="stat">
|
||||
<span class="descriptor">Memory Usage:</span>
|
||||
<span class="value" id="memory-usage"></span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -33,7 +43,11 @@
|
||||
document.getElementById('cpu-usage').textContent = `${data.cpu_percent}%`;
|
||||
document.getElementById('memory-usage').textContent = `${data.memory_percent}%`;
|
||||
})
|
||||
.catch(error => console.error('Error fetching system stats:', error));
|
||||
.catch(error => {
|
||||
console.error('Error fetching system stats:', error);
|
||||
document.getElementById('cpu-usage').textContent = "server not running";
|
||||
document.getElementById('memory-usage').textContent = "server not running";
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch stats every 2 seconds
|
||||
59
system_monitor_overlay/system_monitor_no_heading.html
Normal file
59
system_monitor_overlay/system_monitor_no_heading.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>System Monitor (No Heading)</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.stat {
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.descriptor {
|
||||
font-weight: 300; /* Light */
|
||||
}
|
||||
.value {
|
||||
font-weight: 700; /* Bold */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="stat">
|
||||
<span class="descriptor">CPU Usage:</span>
|
||||
<span class="value" id="cpu-usage"></span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="descriptor">Memory Usage:</span>
|
||||
<span class="value" id="memory-usage"></span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function fetchStats() {
|
||||
fetch('http://localhost:8000/stats')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('cpu-usage').textContent = `${data.cpu_percent}%`;
|
||||
document.getElementById('memory-usage').textContent = `${data.memory_percent}%`;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching system stats:', error);
|
||||
document.getElementById('cpu-usage').textContent = "server not running";
|
||||
document.getElementById('memory-usage').textContent = "server not running";
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch stats every 2 seconds
|
||||
setInterval(fetchStats, 2000);
|
||||
|
||||
// Initial fetch
|
||||
fetchStats();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
22
weather_overlay/README.md
Normal file
22
weather_overlay/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Weather Overlay
|
||||
|
||||
This overlay displays the current weather for a specific location. It's designed to be used as a browser source in streaming software like OBS Studio.
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Add to OBS Studio:**
|
||||
|
||||
* Add a new "Browser" source to your scene.
|
||||
* Set the URL to the `weather.html` file in this directory.
|
||||
* Set the width and height to your desired dimensions.
|
||||
|
||||
2. **Customize Location:**
|
||||
|
||||
To set your location, you need to open the `weather.html` file in a regular web browser (like Firefox or Chrome) first.
|
||||
|
||||
1. Open the `weather.html` file in your browser.
|
||||
2. Enter a city name in the input field and click the "Update" button.
|
||||
3. The overlay will now show the weather for the new location. This location will be saved in your browser's local storage, so it will be remembered the next time you open the overlay.
|
||||
4. You can now add the `weather.html` file as a browser source in OBS Studio. It will use the location you just set.
|
||||
|
||||
**Note:** To reset the location, you can open the browser's developer tools (usually by pressing F12), go to the "Application" or "Storage" tab, find "Local Storage", and delete the `weather_latitude` and `weather_longitude` keys.
|
||||
@@ -1,79 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Weather Overlay</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
#weather-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
#weather-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="weather-container">
|
||||
<img id="weather-icon" src="" alt="Weather Icon">
|
||||
<div>
|
||||
<h1 id="temperature"></h1>
|
||||
<p id="weather-description"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const latitude = 40.71;
|
||||
const longitude = -74.01;
|
||||
const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true`;
|
||||
|
||||
fetch(apiUrl)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const temperature = data.current_weather.temperature;
|
||||
const weatherCode = data.current_weather.weathercode;
|
||||
|
||||
document.getElementById('temperature').textContent = `${temperature}°C`;
|
||||
document.getElementById('weather-description').textContent = getWeatherDescription(weatherCode);
|
||||
document.getElementById('weather-icon').src = getWeatherIcon(weatherCode);
|
||||
})
|
||||
.catch(error => console.error('Error fetching weather data:', error));
|
||||
|
||||
function getWeatherDescription(code) {
|
||||
// This is a simplified mapping. You can expand it for more detailed descriptions.
|
||||
if (code === 0) return "Clear sky";
|
||||
if (code > 0 && code < 4) return "Partly cloudy";
|
||||
if (code > 44 && code < 49) return "Fog";
|
||||
if (code > 50 && code < 58) return "Drizzle";
|
||||
if (code > 60 && code < 68) return "Rain";
|
||||
if (code > 70 && code < 78) return "Snow";
|
||||
if (code > 80 && code < 83) return "Rain showers";
|
||||
if (code > 94) return "Thunderstorm";
|
||||
return "Cloudy";
|
||||
}
|
||||
|
||||
function getWeatherIcon(code) {
|
||||
// This is a simplified mapping. You can expand it with more icons.
|
||||
if (code === 0) return "https://openweathermap.org/img/wn/01d@2x.png"; // Clear sky
|
||||
if (code > 0 && code < 4) return "https://openweathermap.org/img/wn/02d@2x.png"; // Partly cloudy
|
||||
if (code > 44 && code < 49) return "https://openweathermap.org/img/wn/50d@2x.png"; // Fog
|
||||
if (code > 50 && code < 58) return "https://openweathermap.org/img/wn/09d@2x.png"; // Drizzle
|
||||
if (code > 60 && code < 68) return "https://openweathermap.org/img/wn/10d@2x.png"; // Rain
|
||||
if (code > 70 && code < 78) return "https://openweathermap.org/img/wn/13d@2x.png"; // Snow
|
||||
if (code > 80 && code < 83) return "https://openweathermap.org/img/wn/09d@2x.png"; // Rain showers
|
||||
if (code > 94) return "https://openweathermap.org/img/wn/11d@2x.png"; // Thunderstorm
|
||||
return "https://openweathermap.org/img/wn/03d@2x.png"; // Cloudy
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
152
weather_overlay/weather.html
Normal file
152
weather_overlay/weather.html
Normal file
@@ -0,0 +1,152 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Weather Overlay</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
#weather-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
#weather-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
#location-container {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
border: 1px solid white;
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
z-index: 100;
|
||||
}
|
||||
#city-input {
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
#update-button {
|
||||
padding: 5px 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="weather-container">
|
||||
<img id="weather-icon" src="" alt="Weather Icon">
|
||||
<div>
|
||||
<h1 id="temperature"></h1>
|
||||
<p id="weather-description"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="location-container">
|
||||
<input type="text" id="city-input" placeholder="Enter city name">
|
||||
<button id="update-button">Update</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const defaultLatitude = 40.71;
|
||||
const defaultLongitude = -74.01;
|
||||
|
||||
function saveLocation(latitude, longitude) {
|
||||
localStorage.setItem('weather_latitude', latitude);
|
||||
localStorage.setItem('weather_longitude', longitude);
|
||||
}
|
||||
|
||||
function loadLocation() {
|
||||
const latitude = localStorage.getItem('weather_latitude');
|
||||
const longitude = localStorage.getItem('weather_longitude');
|
||||
return { latitude, longitude };
|
||||
}
|
||||
|
||||
function fetchWeather(latitude, longitude) {
|
||||
const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true`;
|
||||
|
||||
fetch(apiUrl)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const temperature = data.current_weather.temperature;
|
||||
const weatherCode = data.current_weather.weathercode;
|
||||
|
||||
document.getElementById('temperature').textContent = `${temperature}°C`;
|
||||
document.getElementById('weather-description').textContent = getWeatherDescription(weatherCode);
|
||||
document.getElementById('weather-icon').src = getWeatherIcon(weatherCode);
|
||||
})
|
||||
.catch(error => console.error('Error fetching weather data:', error));
|
||||
}
|
||||
|
||||
function getCoordinates(city) {
|
||||
const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${city}`;
|
||||
|
||||
fetch(geocodingUrl)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.results && data.results.length > 0) {
|
||||
const latitude = data.results[0].latitude;
|
||||
const longitude = data.results[0].longitude;
|
||||
saveLocation(latitude, longitude);
|
||||
fetchWeather(latitude, longitude);
|
||||
document.getElementById('location-container').style.display = 'none';
|
||||
} else {
|
||||
alert("Could not find city. Please try again.");
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching coordinates:', error));
|
||||
}
|
||||
|
||||
document.getElementById('update-button').addEventListener('click', () => {
|
||||
const city = document.getElementById('city-input').value;
|
||||
if (city) {
|
||||
getCoordinates(city);
|
||||
}
|
||||
});
|
||||
|
||||
function getWeatherDescription(code) {
|
||||
// This is a simplified mapping. You can expand it for more detailed descriptions.
|
||||
if (code === 0) return "Clear sky";
|
||||
if (code > 0 && code < 4) return "Partly cloudy";
|
||||
if (code > 44 && code < 49) return "Fog";
|
||||
if (code > 50 && code < 58) return "Drizzle";
|
||||
if (code > 60 && code < 68) return "Rain";
|
||||
if (code > 70 && code < 78) return "Snow";
|
||||
if (code > 80 && code < 83) return "Rain showers";
|
||||
if (code > 94) return "Thunderstorm";
|
||||
return "Cloudy";
|
||||
}
|
||||
|
||||
function getWeatherIcon(code) {
|
||||
// This is a simplified mapping. You can expand it with more icons.
|
||||
if (code === 0) return "https://openweathermap.org/img/wn/01d@2x.png"; // Clear sky
|
||||
if (code > 0 && code < 4) return "https://openweathermap.org/img/wn/02d@2x.png"; // Partly cloudy
|
||||
if (code > 44 && code < 49) return "https://openweathermap.org/img/wn/50d@2x.png"; // Fog
|
||||
if (code > 50 && code < 58) return "https://openweathermap.org/img/wn/09d@2x.png"; // Drizzle
|
||||
if (code > 60 && code < 68) return "https://openweathermap.org/img/wn/10d@2x.png"; // Rain
|
||||
if (code > 70 && code < 78) return "https://openweathermap.org/img/wn/13d@2x.png"; // Snow
|
||||
if (code > 80 && code < 83) return "https://openweathermap.org/img/wn/09d@2x.png"; // Rain showers
|
||||
if (code > 94) return "https://openweathermap.org/img/wn/11d@2x.png"; // Thunderstorm
|
||||
return "https://openweathermap.org/img/wn/03d@2x.png"; // Cloudy
|
||||
}
|
||||
|
||||
// Initial fetch
|
||||
const storedLocation = loadLocation();
|
||||
if (storedLocation.latitude && storedLocation.longitude) {
|
||||
fetchWeather(storedLocation.latitude, storedLocation.longitude);
|
||||
} else {
|
||||
fetchWeather(defaultLatitude, defaultLongitude);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user