summaryrefslogtreecommitdiff
path: root/morph.html
diff options
context:
space:
mode:
authorSt33v <github@f3rr3t.com>2026-06-10 19:10:38 +1000
committerSt33v <github@f3rr3t.com>2026-06-10 19:10:38 +1000
commite433a99ceddf7168d23377ffc8d585fc80ba8fb2 (patch)
tree32f7ff056e404ae1d4563125c169bd4c535992aa /morph.html
parent59e7c485be1e9be62f0b4cdb7d1130701c1e3c46 (diff)
Add synoptic-morph: 30-day rolling morph timelapse
synopticMorph.sh: rolling-cache implementation. On each run: 1. Pick the last WINDOW_DAYS*4 charts from the archive. 2. For each adjacent pair, fill any missing pair-morph (cached at /mnt/enclave/synoptic/pairs/<chartA>__<chartB>/p_NNNN.png). 3. Evict pair dirs whose slug isn't in the current window. 4. Symlink frames into a temp seq dir with LINGER on each source chart and DWELL on the latest, fade-in 1s and fade-out 2s. 5. Encode to /srv/www/pestrel/morph.mp4 with ffmpeg/h264. Bootstrap: ~50 min CPU on first run (119 pair morphs at ~25s each). Steady state: ~1 min/cycle (1 new pair + concat + encode). synoptic-morph.service: oneshot, TimeoutStartSec=2h to cover the bootstrap, Nice=10 + IOSchedulingClass=idle so it doesn't fight the system for CPU/disk. synoptic.service gains OnSuccess=synoptic-morph.service so the chain fires automatically after each successful chart fetch. morph.html: points at /morph.mp4 now. index.html: "Three-day history" button renamed to "Monthly history", URL /morph.html. setup.sh: installs the new unit + script, provisions /mnt/enclave/synoptic/{pairs,out} when the enclave mount is present. .gitignore: drop .claude/ (transient harness state). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'morph.html')
-rw-r--r--morph.html53
1 files changed, 53 insertions, 0 deletions
diff --git a/morph.html b/morph.html
new file mode 100644
index 0000000..b600234
--- /dev/null
+++ b/morph.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Synoptic — 30-day morph</title>
+<style>
+ body { margin: 0; background: #000; display: flex; justify-content: center; align-items: center; height: 100vh; }
+ video { max-width: 100%; max-height: 100vh; cursor: pointer; }
+ a.nav-button {
+ position: fixed; left: 0;
+ color: #aaa; background: rgba(0,0,0,0.7);
+ padding: 0.45em 0.9em;
+ border: 1px solid #444; border-left: none;
+ border-radius: 0 0.3em 0.3em 0;
+ text-decoration: none; font-family: sans-serif; font-size: 0.95em;
+ }
+ a.nav-button.radars { top: 60%; }
+ a.nav-button.static { top: 75%; }
+ a.nav-button:hover { color: #fff; border-color: #888; }
+ .hint {
+ position: fixed; right: 0.6em; bottom: 0.5em;
+ color: #555; font-family: sans-serif; font-size: 0.75em;
+ pointer-events: none;
+ }
+ #paused-indicator {
+ position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
+ color: #fff; font-family: sans-serif; font-size: 4em;
+ background: rgba(0,0,0,0.5); padding: 0.2em 0.6em; border-radius: 0.2em;
+ display: none; pointer-events: none;
+ }
+ body.paused #paused-indicator { display: block; }
+</style>
+</head>
+<body>
+ <video id="vid" src="/morph.mp4" autoplay muted loop playsinline></video>
+ <a class="nav-button radars" href="https://radar.pestrel.com/">Radars</a>
+ <a class="nav-button static" href="/">Static Chart</a>
+ <div class="hint">click or Shift: pause / resume</div>
+ <div id="paused-indicator">⏸</div>
+ <script>
+ const v = document.getElementById('vid');
+ function toggle() {
+ if (v.paused) { v.play(); document.body.classList.remove('paused'); }
+ else { v.pause(); document.body.classList.add('paused'); }
+ }
+ v.addEventListener('click', toggle);
+ window.addEventListener('keydown', (e) => {
+ if (e.key === 'Shift' && !e.repeat) { e.preventDefault(); toggle(); }
+ });
+ </script>
+</body>
+</html>