<!DOCTYPE html>
<html lang="en">
<head>
<!-- Basic document metadata -->
<meta charset="UTF-8">
<title>Interactive Dashboard Screensaver</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- SEO / description (optional for local use, but good practice) -->
<meta name="description" content="Interactive dashboard-style screensaver with rotating panels and live data.">
<meta name="author" content="">
<!-- <link rel="icon" href="favicon.ico" type="image/x-icon"> -->
<link rel="stylesheet" href="reso/base2.css" type="text/css" />
<style>
</style>
<script>
// Future: initialization code for panels, data fetching, etc.
</script>
</head>
<body>
<!-- Background layer (for animations, gradients, effects) -->
<div id="background-layer"></div>
<!-- Main layout wrapper -->
<div id="layout">
<!-- Top: Rotating panels -->
<main id="panel-container">
<section class="panel" id="panel-1">
<section class="panel" id="panel-1">
<div id="news-container"></div>
</section>
</section>
<section class="panel" id="panel-2">B</section>
<section class="panel" id="panel-3">C</section>
</main>
<!-- Bottom: Static/dynamic info -->
<section id="info-area">
<div class="info-section" id="info-clock"></div>
<div id="market-banner"></div>
<div class="info-section" id="info-ticker"></div>
<div class="info-section" id="info-weather"></div>
</section>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const panels = document.querySelectorAll(".panel");
let index = 0;
// Show the first panel
panels[index].classList.add("active");
// Rotate every 8 seconds
setInterval(() => {
// Hide current panel
panels[index].classList.remove("active");
// Move to next panel (loop back to start)
index = (index + 1) % panels.length;
// Show next panel
panels[index].classList.add("active");
}, 20000);
});
</script>
<script>
function updateClock() {
const clockEl = document.getElementById("info-clock");
const now = new Date();
// Format: HH:MM:SS (24-hour or 12-hour, your choice)
let hours = now.getHours();
let minutes = now.getMinutes();
let seconds = now.getSeconds();
// Pad with leading zeros
hours = String(hours).padStart(2, "0");
minutes = String(minutes).padStart(2, "0");
seconds = String(seconds).padStart(2, "0");
clockEl.textContent = `${hours}:${minutes}:${seconds}`;
}
// Update immediately, then every second
updateClock();
setInterval(updateClock, 1000);
</script>
<!-- Stock API Finnhubb: d542iqhr01qlj84aej7gd542iqhr01qlj84aej80 -->
<script>
// =====================
// Finnhub config
// =====================
const StockyAPI = "d542iqhr01qlj84aej7gd542iqhr01qlj84aej80"; // <-- paste your real Finnhub key here
// =====================
// Ticker groups
// =====================
const tickerGroups = {
"Major Indexes": ["SPY", "QQQ"],
"Tech Leaders": ["MSFT", "NVDA", "GOOG"],
"Sectors": ["XLE", "XLF"]
};
// =====================
// Fetch one symbol from Finnhub
// =====================
// Uses the Finnhub "quote" endpoint:
// https://finnhub.io/api/v1/quote?symbol=SPY&token=KEY
//
// Response shape:
// {
// "c": current price,
// "d": change,
// "dp": percent change,
// "h": high,
// "l": low,
// "o": open,
// "pc": previous close
// }
async function fetchSymbol(sym) {
try {
const url = `https://finnhub.io/api/v1/quote?symbol=${sym}&token=${StockyAPI}`;
const response = await fetch(url);
if (!response.ok) {
console.warn("Finnhub HTTP error for", sym, response.status);
return null;
}
const data = await response.json();
// If Finnhub returns empty or invalid data
if (data == null || typeof data !== "object" || data.c == null) {
console.warn("Finnhub returned no usable data for", sym, data);
return null;
}
const price = data.c; // current price
const change = data.d; // absolute change
const pct = data.dp; // percent change
const high = data.h; // high of day
const low = data.l; // low of day
return {
sym: sym,
price: price.toFixed(2),
change: change.toFixed(2),
pct: pct.toFixed(2),
high: high != null ? high.toFixed(2) : "N/A",
low: low != null ? low.toFixed(2) : "N/A"
};
} catch (err) {
console.error("fetchSymbol ERROR for", sym, err);
return null;
}
}
// =====================
// Build grouped ticker data
// =====================
async function buildTickerData() {
const groups = [];
for (const [label, symbols] of Object.entries(tickerGroups)) {
const group = {
label: label,
blocks: [],
sectorAvgPct: 0,
sectorCount: 0
};
for (const sym of symbols) {
const d = await fetchSymbol(sym);
if (!d) {
group.blocks.push({
sym: sym,
direction: "flat",
priceText: "N/A",
hiloText: "H: N/A L: N/A"
});
continue;
}
const changeNum = parseFloat(d.change);
const pctNum = parseFloat(d.pct);
let direction = "flat";
let arrow = "•";
if (changeNum > 0) {
direction = "up";
arrow = "▲";
} else if (changeNum < 0) {
direction = "down";
arrow = "▼";
}
const sign = changeNum >= 0 ? "+" : "";
const priceText = `${d.price} ${arrow}${sign}${d.pct}%`;
const hiloText = `H: ${d.high} L: ${d.low}`;
if (label === "Sectors" && !isNaN(pctNum)) {
group.sectorAvgPct += pctNum;
group.sectorCount += 1;
}
group.blocks.push({
sym: sym,
direction: direction,
priceText: priceText,
hiloText: hiloText
});
}
if (label === "Sectors" && group.sectorCount > 0) {
const avg = group.sectorAvgPct / group.sectorCount;
if (avg > 0) {
group.heatClass = "sector-strong";
} else if (avg < 0) {
group.heatClass = "sector-weak";
} else {
group.heatClass = "";
}
} else {
group.heatClass = "";
}
groups.push(group);
}
return groups;
}
// =====================
// Render loop
// =====================
let tickerData = [];
let tickerIndex = 0;
let tickerReady = false;
async function updateMarketTicker() {
const data = await buildTickerData();
tickerData = data;
tickerReady = true;
}
function clearElement(el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}
function rotateTicker() {
const tickerEl = document.getElementById("info-ticker");
if (!tickerReady || tickerData.length === 0) {
tickerEl.textContent = "Loading market data…";
return;
}
tickerEl.classList.remove("fade-in");
void tickerEl.offsetWidth; // force reflow for fade re-trigger
clearElement(tickerEl);
const group = tickerData[tickerIndex];
const labelEl = document.createElement("div");
labelEl.className = "ticker-group-label";
if (group.heatClass) {
labelEl.classList.add(group.heatClass);
}
labelEl.textContent = group.label;
tickerEl.appendChild(labelEl);
const rowEl = document.createElement("div");
rowEl.className = "ticker-group-row";
for (const block of group.blocks) {
const blockEl = document.createElement("div");
blockEl.className = "symbol-block";
const nameEl = document.createElement("div");
nameEl.className = "symbol-name";
nameEl.textContent = block.sym;
blockEl.appendChild(nameEl);
const priceEl = document.createElement("div");
priceEl.className = "symbol-price";
priceEl.classList.add(block.direction);
priceEl.textContent = block.priceText;
blockEl.appendChild(priceEl);
const hiloEl = document.createElement("div");
hiloEl.className = "symbol-hilo";
hiloEl.textContent = block.hiloText;
blockEl.appendChild(hiloEl);
rowEl.appendChild(blockEl);
}
tickerEl.appendChild(rowEl);
tickerEl.classList.add("fade-in");
tickerIndex = (tickerIndex + 1) % tickerData.length;
}
// =====================
// Kickoff
// =====================
// Fetch immediately, then every 60 seconds
updateMarketTicker();
setInterval(updateMarketTicker, 60000);
// Rotate every 5 seconds, with initial delay
setTimeout(function () {
rotateTicker();
setInterval(rotateTicker, 5000);
}, 800);
</script>
<script>
async function updateWeather() {
const weatherEl = document.getElementById("info-weather");
// Your exact coordinates
const lat = 38.12254064980286;
const lon = -82.1809568041991;
const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t_weather=true&temperature_unit=fahrenheit&windspeed_unit=mph&hourly=apparent_temperature`;
try {
const response = await fetch(url);
const data = await response.json();
const temp = Math.round(data.current_weather.temperature);
const wind = Math.round(data.current_weather.windspeed);
// Feels-like comes from the hourly array, using the current hour index
const now = new Date();
const hourIndex = now.getHours();
const feels = Math.round(data.hourly.apparent_temperature[hourIndex]);
weatherEl.textContent = `${temp}°F | Feels like: ${feels}°F | Wind ${wind} mph`;
} catch (err) {
weatherEl.textContent = "Weather unavailable";
}
}
// Update immediately, then every 5 minutes
updateWeather();
setInterval(updateWeather, 300000);
</script>
<script>
let newsItems = [];
let newsIndex = 0;
async function loadFoxBusinessNews() {
const RSS_URL = "http://127.0.0.1:5000/rss";
try {
const response = await fetch(RSS_URL);
const text = await response.text();
const parser = new DOMParser();
const xml = parser.parseFromString(text, "text/xml");
// ✅ Paste the debug lines RIGHT HERE
console.log("RAW XML:", xml);
console.log("ALL NODES:", xml.documentElement.innerHTML);
console.log("ITEMS FOUND:", xml.getElementsByTagName("item").length);
// Extract first 6 items
newsItems = [...xml.querySelectorAll("item")].slice(0, 6).map(item => {
const title = item.querySelector("title")?.textContent || "No title";
// Fox Business uses <media:thumbnail> most often
const media = item.querySelector(
"media\\\\:thumbnail, media\\\\:content, enclosure"
);
const imgUrl = media?.getAttribute("url") || null;
return { title, imgUrl };
});
console.log("Loaded items:", newsItems); // debug
showNextHeadline();
} catch (err) {
console.error("News fetch error:", err);
document.getElementById("news-container").textContent =
"Unable to load news.";
}
}
function showNextHeadline() {
if (newsItems.length === 0) return;
const container = document.getElementById("news-container");
container.innerHTML = "";
const item = newsItems[newsIndex];
const div = document.createElement("div");
div.className = "news-item";
if (item.imgUrl) {
const img = document.createElement("img");
img.className = "news-thumb";
img.src = item.imgUrl;
div.appendChild(img);
}
const headline = document.createElement("div");
headline.className = "news-headline";
headline.textContent = item.title;
div.appendChild(headline);
container.appendChild(div);
newsIndex = (newsIndex + 1) % newsItems.length;
}
// Load immediately, refresh every 5 minutes
loadFoxBusinessNews();
setInterval(loadFoxBusinessNews, 300000);
// Rotate displayed headline every 5 seconds
setInterval(showNextHeadline, 5000);
</script>
</body>
</html>