First Push
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user