291 lines
8.1 KiB
HTML
291 lines
8.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Sensor Pico</title>
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
background: #0d1117;
|
|
color: #c9d1d9;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 24px 20px;
|
|
max-width: 400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/* ── Top bar ─────────────────────────────── */
|
|
.topbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 36px;
|
|
}
|
|
.topbar-title {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: #f0f6fc;
|
|
letter-spacing: -0.2px;
|
|
}
|
|
.topbar-sub {
|
|
font-size: 11px;
|
|
color: #484f58;
|
|
margin-top: 1px;
|
|
}
|
|
.settings-btn {
|
|
width: 36px;
|
|
height: 36px;
|
|
background: #161b22;
|
|
border: 1px solid #30363d;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
font-size: 17px;
|
|
transition: border-color 0.15s, background 0.15s;
|
|
flex-shrink: 0;
|
|
}
|
|
.settings-btn:hover { border-color: #8b949e; background: #1c2230; }
|
|
|
|
/* ── Metrics ─────────────────────────────── */
|
|
.metrics { flex: 1; display: flex; flex-direction: column; gap: 10px; }
|
|
|
|
.metric {
|
|
background: #161b22;
|
|
border: 1px solid #30363d;
|
|
border-radius: 12px;
|
|
padding: 20px 22px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
.metric-label {
|
|
font-size: 12px;
|
|
color: #8b949e;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.6px;
|
|
margin-bottom: 4px;
|
|
}
|
|
.metric-icon { font-size: 22px; opacity: 0.7; }
|
|
|
|
.metric-value {
|
|
font-size: 36px;
|
|
font-weight: 300;
|
|
color: #f0f6fc;
|
|
letter-spacing: -1px;
|
|
line-height: 1;
|
|
}
|
|
.metric-value.loading { color: #30363d; }
|
|
.metric-unit {
|
|
font-size: 14px;
|
|
color: #8b949e;
|
|
font-weight: 400;
|
|
margin-left: 3px;
|
|
vertical-align: super;
|
|
font-size: 13px;
|
|
}
|
|
|
|
/* ── Status bar ──────────────────────────── */
|
|
.statusbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-top: 20px;
|
|
padding-top: 16px;
|
|
border-top: 1px solid #21262d;
|
|
}
|
|
.dot {
|
|
width: 7px;
|
|
height: 7px;
|
|
border-radius: 50%;
|
|
background: #3fb950;
|
|
flex-shrink: 0;
|
|
}
|
|
.dot.offline { background: #484f58; animation: none; }
|
|
.dot.live { animation: pulse 2s infinite; }
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.3; }
|
|
}
|
|
.status-text { font-size: 12px; color: #484f58; }
|
|
.status-time { font-size: 12px; color: #484f58; margin-left: auto; }
|
|
|
|
/* ── Settings overlay ────────────────────── */
|
|
.overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0,0,0,0.6);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.2s;
|
|
z-index: 10;
|
|
}
|
|
.overlay.open { opacity: 1; pointer-events: all; }
|
|
|
|
.sheet {
|
|
position: fixed;
|
|
left: 0; right: 0; bottom: 0;
|
|
background: #161b22;
|
|
border-top: 1px solid #30363d;
|
|
border-radius: 16px 16px 0 0;
|
|
padding: 20px;
|
|
max-width: 400px;
|
|
margin: 0 auto;
|
|
transform: translateY(100%);
|
|
transition: transform 0.25s cubic-bezier(0.32,0.72,0,1);
|
|
z-index: 20;
|
|
}
|
|
.sheet.open { transform: translateY(0); }
|
|
|
|
.sheet-handle {
|
|
width: 32px;
|
|
height: 3px;
|
|
background: #30363d;
|
|
border-radius: 2px;
|
|
margin: 0 auto 20px;
|
|
}
|
|
.sheet-title {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: #8b949e;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.7px;
|
|
margin-bottom: 14px;
|
|
}
|
|
.sheet-link {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 14px 16px;
|
|
background: #0d1117;
|
|
border: 1px solid #30363d;
|
|
border-radius: 10px;
|
|
text-decoration: none;
|
|
color: #c9d1d9;
|
|
font-size: 14px;
|
|
margin-bottom: 8px;
|
|
transition: border-color 0.15s;
|
|
}
|
|
.sheet-link:hover { border-color: #8b949e; }
|
|
.sheet-link:last-of-type { margin-bottom: 0; }
|
|
.sheet-arrow { color: #484f58; font-size: 16px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="topbar">
|
|
<div>
|
|
<div class="topbar-title">Sensor Pico</div>
|
|
<div class="topbar-sub">BME280</div>
|
|
</div>
|
|
<div class="settings-btn" onclick="openSettings()">⚙</div>
|
|
</div>
|
|
|
|
<div class="metrics">
|
|
<div class="metric">
|
|
<div>
|
|
<div class="metric-label">Temperatur</div>
|
|
<div>
|
|
<span class="metric-value loading" id="temp">--</span>
|
|
<span class="metric-unit">°C</span>
|
|
</div>
|
|
</div>
|
|
<span class="metric-icon">🌡</span>
|
|
</div>
|
|
<div class="metric">
|
|
<div>
|
|
<div class="metric-label">Luftfeuchtigkeit</div>
|
|
<div>
|
|
<span class="metric-value loading" id="hum">--</span>
|
|
<span class="metric-unit">%</span>
|
|
</div>
|
|
</div>
|
|
<span class="metric-icon">💧</span>
|
|
</div>
|
|
<div class="metric">
|
|
<div>
|
|
<div class="metric-label">Luftdruck</div>
|
|
<div>
|
|
<span class="metric-value loading" id="press">--</span>
|
|
<span class="metric-unit">hPa</span>
|
|
</div>
|
|
</div>
|
|
<span class="metric-icon">🔆</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="statusbar">
|
|
<div class="dot offline" id="dot"></div>
|
|
<span class="status-text" id="status">Verbinde…</span>
|
|
<span class="status-time" id="time"></span>
|
|
</div>
|
|
|
|
<!-- Settings sheet -->
|
|
<div class="overlay" id="overlay" onclick="closeSettings()"></div>
|
|
<div class="sheet" id="sheet">
|
|
<div class="sheet-handle"></div>
|
|
<div class="sheet-title">Einstellungen</div>
|
|
<a href="wlan_config.html" class="sheet-link">
|
|
WLAN konfigurieren
|
|
<span class="sheet-arrow">›</span>
|
|
</a>
|
|
<a href="mqtt_config.html" class="sheet-link">
|
|
MQTT & Messung
|
|
<span class="sheet-arrow">›</span>
|
|
</a>
|
|
</div>
|
|
|
|
<script>
|
|
function openSettings() {
|
|
document.getElementById('overlay').classList.add('open');
|
|
document.getElementById('sheet').classList.add('open');
|
|
}
|
|
function closeSettings() {
|
|
document.getElementById('overlay').classList.remove('open');
|
|
document.getElementById('sheet').classList.remove('open');
|
|
}
|
|
|
|
function pad(n) { return n < 10 ? '0' + n : n; }
|
|
|
|
function fetchData() {
|
|
fetch('/sensor-data.json')
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(d) {
|
|
var t = document.getElementById('temp');
|
|
var h = document.getElementById('hum');
|
|
var p = document.getElementById('press');
|
|
t.textContent = d.temperature.toFixed(1);
|
|
h.textContent = d.humidity.toFixed(1);
|
|
p.textContent = d.pressure.toFixed(1);
|
|
t.classList.remove('loading');
|
|
h.classList.remove('loading');
|
|
p.classList.remove('loading');
|
|
|
|
var dot = document.getElementById('dot');
|
|
dot.className = 'dot live';
|
|
document.getElementById('status').textContent = 'Live';
|
|
|
|
var now = new Date();
|
|
document.getElementById('time').textContent =
|
|
pad(now.getHours()) + ':' + pad(now.getMinutes()) + ':' + pad(now.getSeconds());
|
|
})
|
|
.catch(function() {
|
|
var dot = document.getElementById('dot');
|
|
dot.className = 'dot offline';
|
|
document.getElementById('status').textContent = 'Keine Daten';
|
|
document.getElementById('time').textContent = '';
|
|
});
|
|
}
|
|
|
|
fetchData();
|
|
setInterval(fetchData, 3000);
|
|
</script>
|
|
</body>
|
|
</html>
|