diff options
Diffstat (limited to 'docs/prototypes/2026-07-21-night-watch-screensaver-prototype-1.html')
| -rw-r--r-- | docs/prototypes/2026-07-21-night-watch-screensaver-prototype-1.html | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/docs/prototypes/2026-07-21-night-watch-screensaver-prototype-1.html b/docs/prototypes/2026-07-21-night-watch-screensaver-prototype-1.html new file mode 100644 index 0000000..7ccc071 --- /dev/null +++ b/docs/prototypes/2026-07-21-night-watch-screensaver-prototype-1.html @@ -0,0 +1,165 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>Dupre Night Watch — idle-stage screensaver prototype 1</title> +<style> + :root{ + --ground:#151311; --panel:#100f0f; --well:#0a0c0d; --raise:#1a1917; + --silver:#bfc4d0; --cream:#f3e7c5; --steel:#969385; --dim:#7c838a; + --slate:#424f5e; --slate-hi:#54677d; --wash:#2c2f32; + --pass:#74932f; --fail:#cb6b4d; + --phos:#7fe0a0; --phos-dim:#1c3626; --vfd:#63e6c8; + --sevgrn:#57d357; --sevred:#e2543f; --sevoff:#1a1613; + --jewel-r:#ff5b45; --jewel-a:#ffb43a; --jewel-g:#6fce33; + --gold:#e2a038; --gold-hi:#ffbe54; + --amber-grad-top:#f2c76a; --amber-grad-mid:#d29638; --amber-grad-bot:#8f671f; + --amber-edge:#7d5c16; --amber-warn:#f0b552; + --glow-hi:255,190,84; --glow-lo:226,160,56; + --mono:"BerkeleyMono Nerd Font","Berkeley Mono",monospace; + --pulse-rate:1s; + } + * { box-sizing: border-box; } + body { + margin: 0; min-height: 100vh; background: #050505; + color: var(--silver); font-family: var(--mono); + display: flex; flex-direction: column; align-items: center; + gap: 10px; padding: 24px; + zoom: 1.7; + cursor: none; /* it's a screensaver */ + } + .board { + display: grid; + grid-template-columns: repeat(4, auto); + gap: 14px 18px; + align-items: center; + justify-items: center; + } + .cell { + background: linear-gradient(180deg, #16140f, #0e0d0b); + border: 1px solid #262320; + border-radius: 10px; + padding: 8px 10px 6px; + display: flex; flex-direction: column; align-items: center; gap: 4px; + } + /* kit instruments are spans with fixed CSS sizes; a flex host blockifies + them (the gallery's card grid does this implicitly) */ + .cell > div:first-child { display: flex; justify-content: center; } + .cell .lbl { + color: var(--steel); font-size: 8px; letter-spacing: 0.22em; + text-transform: uppercase; + } + .wide { grid-column: span 2; } + .masthead { + color: var(--steel); font-size: 10px; letter-spacing: 0.35em; + text-transform: uppercase; padding-bottom: 2px; + } + .masthead b { color: var(--gold); font-weight: 400; } +</style> +</head> +<body> + <div class="masthead"><b>DUPRE</b> · NIGHT WATCH</div> + <div class="board" id="board"></div> + +<script src="widgets.js"></script> +<script> +const cell = (label, span) => { + const c = document.createElement("div"); + c.className = "cell" + (span ? " wide" : ""); + const host = document.createElement("div"); + c.appendChild(host); + const l = document.createElement("div"); + l.className = "lbl"; l.textContent = label; + c.appendChild(l); + document.getElementById("board").appendChild(c); + return host; +}; + +/* ---- top row: timekeeping ---------------------------------------------- */ +const flap = DUPRE.splitFlap(cell("local time", true), { + cells: 5, chars: " 0123456789:", flapMs: 70, animate: true, +}); +const clock = DUPRE.analogClock(cell("movement")); +const seg = DUPRE.sevenSeg(cell("min : sec")); + +/* ---- second row: watchstanding ----------------------------------------- */ +DUPRE.radarSweep(cell("scan"), { contacts: [[40, .5], [150, .72], [265, .38], [330, .6]] }); +const scope = DUPRE.scope(cell("trace")); +const strip = DUPRE.stripChart(cell("drift"), { samples: 60, value: 0.5 }); +const eq = DUPRE.eqBars(cell("spectrum"), { bands: 11, cells: 9 }); + +/* ---- third row: machine room ------------------------------------------- */ +const vu = DUPRE.vuPair(cell("programme")); +const blinken = DUPRE.blinkenlights(cell("core")); +const tape = DUPRE.tapeCounter(cell("elapsed"), { value: 0, wheels: 6, redFrom: 4 }); +const lamps = DUPRE.statusLamps(cell("systems"), { states: [0, 0, 1, 0, 4] }); + +/* ---- bottom row: comms + orders ---------------------------------------- */ +DUPRE.voiceLoop(cell("loops")); +const marquee = DUPRE.vfdMarquee(cell("wire", true), { + width: 200, + msgs: [ + "DUPRE NIGHT WATCH · ALL SYSTEMS NOMINAL", + "IDLE PIPELINE ARMED · DIM 5 · LOCK 7 · DPMS 10", + "NEXT ORDER ON THE TELEGRAPH · STAND EASY", + ], +}); +const tele = DUPRE.telegraphIndicator(cell("engine order"), { + states: ["AHEAD", "HALF", "SLOW", "STOP", "STAND BY", "FULL"], index: 3, +}); + +/* ---- the page owns the clock (tick contract) ---------------------------- */ +let last = { m: -1 }; +const timeTick = () => { + const d = new Date(); + clock.set(d.getHours(), d.getMinutes(), d.getSeconds()); + seg.set(d.getMinutes() * 60 + d.getSeconds()); + if (d.getMinutes() !== last.m) { + last.m = d.getMinutes(); + const hh = String(d.getHours()).padStart(2, "0"); + const mm = String(d.getMinutes()).padStart(2, "0"); + flap.setText(hh + ":" + mm); + } +}; +timeTick(); +setInterval(timeTick, 1000); + +/* signals: slow random walks + a breathing sine */ +let vl = 0.4, vr = 0.5, ph = 0, driftV = 0.5; +setInterval(() => { + vl = Math.max(0.05, Math.min(0.95, vl + (Math.random() - 0.5) * 0.16)); + vr = Math.max(0.05, Math.min(0.95, vr + (Math.random() - 0.5) * 0.16)); + vu.set(vl, vr); + eq.set(Array.from({ length: 11 }, (_, i) => + Math.max(0, Math.min(1, (vl + vr) / 2 + Math.sin(ph + i * 0.7) * 0.3 + (Math.random() - 0.5) * 0.2)))); +}, 110); + +setInterval(() => { + ph += 0.25; + const n = 64; + scope.set(Array.from({ length: n }, (_, i) => + Math.sin(ph + (i / n) * Math.PI * 4) * (0.5 + 0.3 * Math.sin(ph * 0.13))), 6.2); +}, 90); + +setInterval(() => { + driftV = Math.max(0.08, Math.min(0.92, driftV + (Math.random() - 0.5) * 0.09)); + strip.push(driftV); +}, 450); + +setInterval(() => blinken.tick(), 260); +setInterval(() => marquee.tick(), 40); + +let elapsed = 0; +setInterval(() => tape.set(++elapsed), 1000); + +/* the watch changes: a lamp flickers its state every so often, and every + couple of minutes the telegraph rings a new order */ +setInterval(() => { + const i = Math.floor(Math.random() * 5); + lamps.set(i, Math.random() < 0.7 ? 0 : Math.random() < 0.5 ? 1 : 4); +}, 4200); +setInterval(() => tele.set(Math.floor(Math.random() * 6)), 120000); +</script> +</body> +</html> |
