173 lines
5.6 KiB
HTML
173 lines
5.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
|
<title>Cal Display - Custom</title>
|
|
<style>
|
|
:root {
|
|
--bg: #0f1117;
|
|
--text: #e0e0e0;
|
|
--text-dim: #888;
|
|
--accent: #4A90D9;
|
|
--radius: 12px;
|
|
}
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
html, body {
|
|
width: 100vw; height: 100vh; height: 100dvh;
|
|
overflow: hidden;
|
|
font-family: -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
user-select: none; -webkit-user-select: none; touch-action: manipulation;
|
|
}
|
|
#custom-container {
|
|
width: 100%; height: 100%;
|
|
padding: clamp(10px, 2vw, 28px);
|
|
overflow-y: auto;
|
|
position: relative;
|
|
}
|
|
.alarm-overlay {
|
|
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
|
width: 100vw; height: 100vh; height: 100dvh;
|
|
background: rgba(15, 17, 23, 0.92);
|
|
display: flex; align-items: center; justify-content: center;
|
|
z-index: 100;
|
|
}
|
|
.alarm-overlay.hidden { display: none; }
|
|
.alarm-overlay-box {
|
|
text-align: center;
|
|
padding: clamp(24px, 5vw, 56px) clamp(32px, 6.5vw, 72px);
|
|
background: var(--card-bg); border-radius: clamp(12px, 2vw, 24px);
|
|
border: 2px solid #E05555;
|
|
animation: overlay-in 0.3s ease;
|
|
}
|
|
@keyframes overlay-in {
|
|
from { transform: scale(0.85); opacity: 0; }
|
|
to { transform: scale(1); opacity: 1; }
|
|
}
|
|
.alarm-overlay-icon { font-size: clamp(40px, 7vw, 80px); margin-bottom: clamp(8px, 1.5vh, 16px); }
|
|
.alarm-overlay-title { font-size: clamp(18px, 2.75vw, 36px); font-weight: 700; margin-bottom: clamp(6px, 1vh, 12px); color: #E05555; }
|
|
.alarm-overlay-info { font-size: clamp(14px, 2vw, 26px); color: var(--text-dim); margin-bottom: clamp(16px, 3vh, 32px); line-height: 1.6; }
|
|
.alarm-dismiss-btn {
|
|
padding: clamp(10px, 1.75vw, 20px) clamp(32px, 6vw, 64px);
|
|
font-size: clamp(14px, 2.25vw, 26px); font-weight: 600;
|
|
background: #E05555; color: #fff;
|
|
border: none; border-radius: clamp(8px, 1.25vw, 14px); cursor: pointer;
|
|
font-family: inherit;
|
|
}
|
|
.alarm-dismiss-btn:active { opacity: 0.8; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div id="custom-container">
|
|
{{ custom_html | safe }}
|
|
</div>
|
|
|
|
<!-- Custom notification overlay (hidden by default) -->
|
|
<div id="custom-notify-overlay" class="alarm-overlay hidden">
|
|
<div class="alarm-overlay-box">
|
|
<div class="alarm-overlay-icon" id="custom-notify-icon">🔔</div>
|
|
<div class="alarm-overlay-title" id="custom-notify-title">提醒</div>
|
|
<div class="alarm-overlay-info" id="custom-notify-info"></div>
|
|
<button id="custom-notify-dismiss" class="alarm-dismiss-btn">知道了</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
'use strict';
|
|
|
|
// Custom notification overlay
|
|
var customNotifyActive = false;
|
|
var customNotifyAutoDismiss = null;
|
|
var customNotifyAudioCtx = null;
|
|
|
|
function showCustomNotification(data) {
|
|
if (customNotifyActive) return;
|
|
customNotifyActive = true;
|
|
|
|
var overlay = document.getElementById('custom-notify-overlay');
|
|
var iconEl = document.getElementById('custom-notify-icon');
|
|
var titleEl = document.getElementById('custom-notify-title');
|
|
var infoEl = document.getElementById('custom-notify-info');
|
|
|
|
if (iconEl) iconEl.innerHTML = data.icon || '🔔';
|
|
if (titleEl) titleEl.textContent = data.title || '提醒';
|
|
if (infoEl) infoEl.textContent = data.message || '';
|
|
if (overlay) overlay.classList.remove('hidden');
|
|
|
|
if (data.sound !== false) {
|
|
playNotifySound();
|
|
}
|
|
|
|
customNotifyAutoDismiss = setTimeout(function () {
|
|
dismissCustomNotification();
|
|
}, 5000);
|
|
}
|
|
|
|
function playNotifySound() {
|
|
try {
|
|
var ctx = new (window.AudioContext || window.webkitAudioContext)();
|
|
customNotifyAudioCtx = ctx;
|
|
var t = ctx.currentTime;
|
|
[0, 0.3, 0.6].forEach(function (offset) {
|
|
var osc = ctx.createOscillator();
|
|
var gain = ctx.createGain();
|
|
osc.type = 'sine';
|
|
osc.frequency.value = 880;
|
|
gain.gain.setValueAtTime(1.0, t + offset);
|
|
gain.gain.exponentialRampToValueAtTime(0.001, t + offset + 0.15);
|
|
osc.connect(gain);
|
|
gain.connect(ctx.destination);
|
|
osc.start(t + offset);
|
|
osc.stop(t + offset + 0.15);
|
|
});
|
|
setTimeout(function () { ctx.close(); }, 2000);
|
|
} catch (_) {}
|
|
}
|
|
|
|
function dismissCustomNotification() {
|
|
customNotifyActive = false;
|
|
var overlay = document.getElementById('custom-notify-overlay');
|
|
if (overlay) overlay.classList.add('hidden');
|
|
if (customNotifyAudioCtx) {
|
|
try { customNotifyAudioCtx.close(); } catch (_) {}
|
|
customNotifyAudioCtx = null;
|
|
}
|
|
if (customNotifyAutoDismiss) {
|
|
clearTimeout(customNotifyAutoDismiss);
|
|
customNotifyAutoDismiss = null;
|
|
}
|
|
}
|
|
|
|
var customNotifyDismissBtn = document.getElementById('custom-notify-dismiss');
|
|
if (customNotifyDismissBtn) {
|
|
customNotifyDismissBtn.addEventListener('click', dismissCustomNotification);
|
|
}
|
|
|
|
// SSE: listen for control commands (force_refresh / mode_change / custom_notification)
|
|
function connectSSE() {
|
|
var es = new EventSource('/api/events');
|
|
es.onmessage = function (e) {
|
|
try {
|
|
var msg = JSON.parse(e.data);
|
|
if (msg.type === 'force_refresh' || msg.type === 'display_mode_changed') {
|
|
location.reload();
|
|
} else if (msg.type === 'custom_notification') {
|
|
showCustomNotification(msg.data);
|
|
}
|
|
} catch (_) {}
|
|
};
|
|
es.onerror = function () {
|
|
es.close();
|
|
setTimeout(connectSSE, 5000);
|
|
};
|
|
}
|
|
connectSSE();
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|