aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes/2026-07-21-night-watch-screensaver-prototype-1.html
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-22 15:15:02 -0500
committerCraig Jennings <c@cjennings.net>2026-07-22 15:15:02 -0500
commitf1c661f50d6af2a115fc1430ae0478f2acef2dec (patch)
treeb894ea2e14327794bb75671b962f37185cff22ea /docs/prototypes/2026-07-21-night-watch-screensaver-prototype-1.html
parentf594068bed75302aba50793ccf5e6ce9cf53c7f8 (diff)
downloadarchsetup-f1c661f50d6af2a115fc1430ae0478f2acef2dec.tar.gz
archsetup-f1c661f50d6af2a115fc1430ae0478f2acef2dec.zip
docs(settings-panel): seal the prototype arc and open the build
Prototypes P12-P37 land, along with the night-watch and levels-compare pages plus the shared-engine updates and the kit-candidate file. The spec flips from DRAFT through READY to DOING with rewritten implementation phases and seven recorded build findings. The build tasks are filed in todo.org, and phase 1 is closed against the dotfiles commits.
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.html165
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>