Files
brammp 3662a6efa8 Fixed server port being open after quit and date being 1970 if missing
Date wil be hidden if no set,
and of GEO data is also missing "Not available" will be shown under "Photo info"
2026-09-07 23:35:14 +02:00

225 lines
6.7 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Ambient SlideShow</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Roboto", sans-serif;
}
body {
background: #000;
color: #fff;
overflow: hidden;
}
.slideshow {
position: fixed;
inset: 0;
}
.slide {
position: absolute;
inset: 0;
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
opacity: 0;
transition: opacity 1.5s ease-in-out;
}
.visible {
opacity: 1;
}
.overlay {
position: absolute;
inset: 0;
background: linear-gradient(
to top,
rgba(0, 0, 0, 0.65) 0%,
rgba(0, 0, 0, 0.0) 40%
);
pointer-events: none;
}
.info-bar {
position: absolute;
bottom: 40px;
left: 40px;
right: 40px;
display: flex;
justify-content: space-between;
align-items: flex-end;
}
.left-info {
display: flex;
align-items: center;
gap: 25px;
}
.right-info {
position: absolute;
right: 0;
}
.weather {
display: flex;
align-items: center;
font-size:2rem;
opacity: 0.8;
}
.weather-icon {
margin-top: 44px;
width: 80px;
height: 80px;
opacity: 0.8;
}
.weather-text {
padding-left: 5px;
text-align: right;
margin-top: 44px;
opacity: 0.8;
}
.info-bar-header{
text-align: right;
font-size: 1rem;
font-weight: 400;
margin-bottom: 0.4rem;
letter-spacing: 0.05em;
}
.photo-GeoData {
text-align: right;
font-size: 1.5rem;
font-weight: 400;
margin-bottom: 0.1rem;
}
.photo-Date {
text-align: right;
font-size: 1rem;
opacity: 0.8;
}
.clock-time {
font-size: 4rem;
font-weight: 400;
letter-spacing: 0.05em;
}
.clock-date {
font-size: 2rem;
opacity: 0.8;
margin-top: 0.25rem;
}
</style>
</head>
<body>
<div class="slideshow">
<div id="photoA" class="slide visible" style="background-image:url('');"></div>
<div id="photoB" class="slide" style="background-image:url('');"></div>
<div class="overlay"></div>
<div class="info-bar">
<div class="left-info">
<div class="clock">
<div class="weather">
<div></div>
<img id="weather-icon" class="weather-icon" src="" alt="">
<div id="weather-text" class="weather-text"></div>
</div>
<div class="clock-time" id="clock-time"></div>
<div class="clock-date" id="clock-date"></div>
</div>
</div>
<div class="right-info">
<div class="info-bar-header">Photo info:</div>
<div class="photo-GeoData" id="photo-GeoData"></div>
<div class="photo-Date" id="photo-Date"></div>
</div>
</div>
<script>
// --- Slideshow ---
async function start() {
const res = await fetch('/images.json');
const images = await res.json();
if (!images.length) return;
let index = 0;
let showingA = true;
const photoA = document.getElementById('photoA');
const photoB = document.getElementById('photoB');
// Start with A visible
photoA.style.backgroundImage = 'url(' + images[index] + ')';
photoA.classList.add('visible');
photoInfo(images[index])
document.getElementById('photo-Date').textContent = ""
document.getElementById('photo-GeoData').textContent = ""
setInterval(() => {
index = (index + 1) % images.length;
if (showingA) {
photoB.style.backgroundImage = 'url(' + images[index] + ')';
photoB.classList.add('visible');
photoA.classList.remove('visible');
} else {
photoA.style.backgroundImage = 'url(' + images[index] + ')';
photoA.classList.add('visible');
photoB.classList.remove('visible');
}
photoInfo(images[index])
showingA = !showingA;
}, 5 * 60 * 1000);
}
start();
// --- Photo Info ---
async function photoInfo(photo) {
const response = await fetch('exif?file='+photo);
const data = await response.json();
let formatted = new Date(data.capture_date).toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric'
});
if (typeof data.geo_data == 'undefined' || data.geo_data == null) {
document.getElementById('photo-GeoData').textContent = ""
}else if(typeof data.geo_data.GeoData.features[0].properties.name !== 'undefined' & data.geo_data.GeoData.features[0].properties.name !== null & data.geo_data.GeoData.features[0].properties.name !== 'undefined' ){
document.getElementById('photo-GeoData').textContent = data.geo_data.GeoData.features[0].properties.name + " - " + data.geo_data.GeoData.features[0].properties.city+ ", " + data.geo_data.GeoData.features[0].properties.country;
}else{
document.getElementById('photo-GeoData').textContent = data.geo_data.GeoData.features[0].properties.city+ ", " + data.geo_data.GeoData.features[0].properties.country;
}
if((typeof data.capture_date == 'undefined' || data.capture_date == null) && (typeof data.geo_data == 'undefined' || data.geo_data == null)){
document.getElementById('photo-Date').textContent = "Not available"
}else if (typeof data.capture_date == 'undefined' || data.capture_date == null) {
document.getElementById('photo-Date').textContent = ""
}else{
document.getElementById('photo-Date').textContent = formatted;
}
}
// --- Clock ---
function updateClock() {
const now = new Date();
document.getElementById('clock-time').textContent =
now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
document.getElementById('clock-date').textContent =
now.toLocaleDateString([], { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
}
updateClock();
setInterval(updateClock, 1000);
// --- Weather ---
async function loadWeather() {
const response = await fetch('/weather');
const data = await response.json();
const temp = data.temperature;
const icon = data.symbol;
document.getElementById("weather-text").textContent = `${temp}°C`;
document.getElementById("weather-icon").src =`${icon}`;
}
loadWeather();
setInterval(loadWeather, 10 * 60 * 1000); // refresh every 10 min
</script>
</body>
</html>