Date wil be hidden if no set, and of GEO data is also missing "Not available" will be shown under "Photo info"
225 lines
6.7 KiB
HTML
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>
|