aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-20.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-02-desktop-settings-panel-prototype-20.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-02-desktop-settings-panel-prototype-20.html')
-rw-r--r--docs/prototypes/2026-07-02-desktop-settings-panel-prototype-20.html360
1 files changed, 360 insertions, 0 deletions
diff --git a/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-20.html b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-20.html
new file mode 100644
index 0000000..9f01322
--- /dev/null
+++ b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-20.html
@@ -0,0 +1,360 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Desktop Settings — Prototype 20 (the switch matrix)</title>
+<link rel="stylesheet" href="desktop-settings-shared.css">
+<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;
+ }
+ .plate {
+ background:
+ repeating-linear-gradient(90deg, #ffffff05 0 1px, transparent 1px 3px),
+ linear-gradient(180deg, #1d1a16, #141210);
+ border: 1px solid #262320;
+ border-radius: 10px;
+ padding: 10px;
+ }
+ .plate .ds-engrave { margin: 0 2px 8px; }
+ .vcell { display: flex; flex-direction: column; align-items: center; }
+ .khost { display: flex; justify-content: center; }
+
+ /* the matrix: switches down, presets across */
+ .mx { display: grid; grid-template-columns: 96px repeat(4, 1fr); row-gap: 6px; align-items: center; }
+ .mx .mx-cell { display: flex; align-items: center; justify-content: center; min-height: 22px; }
+ .mx .mx-live { justify-content: flex-start; }
+ .mx .dupre-rocker { width: 50px; height: 32px; }
+ .mx .dupre-rocker .dupre-half { font-size: 8px; }
+ .mx-preset {
+ background-image: linear-gradient(180deg, var(--raise), #131110);
+ border: 1px solid #33302b; border-bottom-width: 3px; border-radius: 6px;
+ padding: 5px 2px; text-align: center; cursor: pointer; color: var(--silver);
+ font-size: 8.5px; letter-spacing: 0.02em; width: 100%; margin: 0 2px;
+ overflow: hidden; font-family: var(--mono);
+ }
+ .mx-preset:hover { border-color: var(--gold); color: var(--gold); }
+ .mx-preset.cur {
+ border-color: var(--gold); border-bottom-width: 1px; transform: translateY(2px);
+ background-image: linear-gradient(180deg, #2a2618, #1d1a12); color: var(--cream);
+ }
+ .mx-name { color: var(--steel); font-size: 8px; letter-spacing: 0.1em; text-transform: uppercase; margin-left: 6px; }
+ .mx-lamp { width: 11px; height: 11px; border-radius: 50%;
+ background: radial-gradient(circle at 38% 32%, #2e2a24, #171310); border: 1px solid #060505; }
+ .mx-lamp.on { background: radial-gradient(circle at 38% 32%, #a9c95f, var(--pass) 60%, #3c4d18);
+ box-shadow: 0 0 7px rgba(116,147,47,.7); }
+ /* punch cells: the chart's dot vocabulary, scoped for the matrix */
+ .mx .pc-dot, .mx .pc-dash { pointer-events: none; }
+ .mx .pc-dot { width: 6px; height: 6px; border-radius: 50%; }
+ .mx .pc-dot.pc-set { background: #cfd3cf; }
+ .mx .pc-dot.pc-clear { border: 1px solid var(--steel); opacity: .6; }
+ .mx .pc-dash { width: 6px; height: 1px; background: var(--wash); }
+ .mx .mx-punch { cursor: pointer; border-radius: 4px; height: 22px; }
+ .mx .mx-punch:hover { background: #ffffff10; }
+ .mx .mx-pwr { font-size: 7.5px; color: var(--steel); font-family: var(--mono); }
+ .mx-row-live .mx-pwr { color: #f2f4f2; }
+
+ .lvrow { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
+ .lvrow .lv-name { color: var(--silver); font-size: 10px; width: 48px; letter-spacing: 0.06em; }
+ .timer-row { display: flex; justify-content: center; margin-top: 4px; }
+ .tcell { position: relative; }
+ .bat-oly { position: absolute; top: -2px; right: -4px; }
+ .bat-oly svg { width: 46px; height: 59px; }
+
+ .pointing { display: flex; justify-content: center; gap: 24px; }
+ .pointing .dupre-rocker { width: 50px; height: 32px; }
+ .pointing .dupre-rocker .dupre-half { font-size: 8px; }
+
+ .wprow {
+ display: flex; align-items: center; gap: 8px;
+ background-image: linear-gradient(180deg, var(--raise), #131110);
+ border: 1px solid #33302b; border-radius: 8px;
+ padding: 8px 10px; cursor: pointer; color: var(--silver); font-size: 12px;
+ }
+ .wprow:hover { border-color: var(--gold); color: var(--gold); }
+ .wprow .k-state { margin-left: auto; color: var(--dim); font-size: 10px; }
+</style>
+</head>
+<body class="ds-stage">
+ <div class="ds-caption">
+ <b>Prototype 20 — the switch matrix.</b> The panel's axis is the
+ switches: each row is a live switch, the four preset buttons run across
+ the top (names only), and the white punch lights under each preset
+ program what it recalls. TPAD and MOUSE (never scene-touched) drop to
+ their own strip below the levels. Screen, keyboard, and power are
+ horizontal thumb slides. The slide-rule dial is retired.
+ </div>
+
+ <div class="ds-bar"><span>19:24</span><span class="gear">⚙</span></div>
+
+ <div class="ds-panel" id="panel">
+ <div class="ds-face">
+ <span class="glyph">⚙</span>
+ <span class="title">Settings</span>
+ <span class="sub" id="scene-sub"></span>
+ <span class="x">✕</span>
+ </div>
+ <div id="body"></div>
+ <div class="ds-toast" id="toast"></div>
+ <div class="ds-subview" id="subview"></div>
+ </div>
+
+<script src="widgets.js"></script>
+<script src="dupre-kit-additions.js"></script>
+<script src="desktop-settings-shared.js"></script>
+<script>
+const h = DS.h;
+const body = document.getElementById("body");
+const subview = document.getElementById("subview");
+DS.bindToast(document.getElementById("toast"));
+
+const SCENES = Object.keys(DS.SCENES); // Focus Presentation Battery Night
+const MX_ROWS = [
+ { key: "autodim", name: "DIM", kind: "rocker" },
+ { key: "nightlight", name: "NIGHT", kind: "rocker" },
+ { key: "dnd", name: "DND", kind: "rocker" },
+ { key: "caffeine", name: "CAFF", kind: "lamp" },
+ { key: "powerprofile", name: "PWR", kind: "power" },
+];
+const PWR_LETTER = { performance: "P", balanced: "B", saver: "S" };
+const LETTER_PWR = { P: "performance", B: "balanced", S: "saver" };
+const PWR_CYCLE = ["P", "B", "S", null];
+let booting = true;
+let caffMinutes = 0;
+let caffHold = false;
+let timerHost = null;
+
+const debounce = (fn, ms) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); }; };
+
+function openWallpaper() {
+ subview.innerHTML = "";
+ subview.appendChild(DS.wallpaperSubview(() => subview.classList.remove("open")));
+ subview.classList.add("open");
+}
+
+function syncCaffeine(el) {
+ const on = caffHold || caffMinutes > 0;
+ if (!booting && on !== DS.state.caffeine) DS.setToggle("caffeine", el);
+}
+const syncCaffeineSoon = debounce(() => syncCaffeine(timerHost), 450);
+
+function scaleSvg(host, w) {
+ const svg = host.querySelector("svg");
+ const [, , vw, vh] = svg.getAttribute("viewBox").split(" ").map(Number);
+ svg.setAttribute("width", w);
+ svg.setAttribute("height", Math.round(w * vh / vw));
+}
+
+function tameTimer(host) {
+ const svg = host.querySelector("svg");
+ const NS = "http://www.w3.org/2000/svg";
+ const defs = document.createElementNS(NS, "defs");
+ defs.innerHTML = `<radialGradient id="p20Steel" cx="38%" cy="32%" r="80%">
+ <stop offset="0" stop-color="#c9c4b8"/><stop offset="1" stop-color="#6e685c"/>
+ </radialGradient>`;
+ svg.insertBefore(defs, svg.firstChild);
+ svg.querySelectorAll('[fill="url(#mtRed)"]').forEach((n) => n.setAttribute("fill", "url(#p20Steel)"));
+ [...svg.querySelectorAll("text")].forEach((t) => {
+ if (t.textContent === "TURN TO START") t.remove();
+ if (t.textContent === "PUSH TO STOP") t.textContent = "WIND · PUSH STOPS";
+ if (t.textContent === "OFF") t.setAttribute("fill", "var(--gold-hi)");
+ });
+ [...svg.querySelectorAll("line")].forEach((l) => {
+ if (l.getAttribute("stroke") === "#e0523a") l.setAttribute("stroke", "var(--gold)");
+ });
+ svg.querySelectorAll('[stroke="#5c150c"]').forEach((n) => n.setAttribute("stroke", "#3c382f"));
+ [...svg.querySelectorAll("path")].find((p) => p.getAttribute("d").startsWith("M 122 14"))?.remove();
+ [...svg.querySelectorAll("polygon")].find((p) => p.getAttribute("points").startsWith("136,26"))?.remove();
+}
+
+// program cell value for scene n, row r
+function cellOf(n, r) {
+ const sets = DS.SCENES[n].sets;
+ if (r.kind === "power") return PWR_LETTER[sets.powerprofile] || null;
+ return r.key in sets ? !!sets[r.key] : null;
+}
+function paintPunch(c, v) {
+ if (v === true) c.innerHTML = '<span class="pc-dot pc-set"></span>';
+ else if (v === false) c.innerHTML = '<span class="pc-dot pc-clear"></span>';
+ else if (typeof v === "string") { c.innerHTML = ""; c.textContent = v; c.classList.add("mx-pwr"); return; }
+ else c.innerHTML = '<span class="pc-dash"></span>';
+ c.classList.remove("mx-pwr");
+}
+
+function render() {
+ booting = true;
+ body.innerHTML = "";
+ const col = h("div", "ds-col");
+
+ // ---- plate 1: the matrix ------------------------------------------------
+ const plate = h("div", "plate");
+ plate.appendChild(DS.engrave("Programs"));
+ const mx = h("div", "mx");
+
+ // header row: empty corner + preset buttons (names only)
+ mx.appendChild(h("div", "mx-cell"));
+ SCENES.forEach((n) => {
+ const b = h("button", "mx-preset" + (DS.state.scene === n ? " cur" : ""), n);
+ b.onclick = () => DS.applyScene(n, b);
+ const wrap = h("div", "mx-cell");
+ wrap.appendChild(b);
+ mx.appendChild(wrap);
+ });
+
+ // switch rows
+ MX_ROWS.forEach((r) => {
+ const live = h("div", "mx-cell mx-live");
+ if (r.kind === "rocker") {
+ const host = h("div", "khost");
+ DUPRE.rocker(host, {
+ on: DS.state[r.key],
+ onLabel: r.name,
+ offLabel: "",
+ quiet: true,
+ accent: "green",
+ onChange: (on) => { if (!booting && on !== DS.state[r.key]) DS.setToggle(r.key, host); },
+ });
+ live.appendChild(host);
+ } else if (r.kind === "lamp") {
+ const lamp = h("span", "mx-lamp" + (DS.state.caffeine ? " on" : ""));
+ live.appendChild(lamp);
+ live.appendChild(h("span", "mx-name", r.name));
+ } else {
+ const host = h("div", "khost");
+ DUPRE.thumbSlideH(host, {
+ width: 86,
+ positions: ["P", "B", "S"],
+ index: DS.PROFILES.findIndex((p) => p.id === DS.state.powerprofile),
+ onChange: (i) => {
+ if (booting) return;
+ if (DS.PROFILES[i].id !== DS.state.powerprofile) DS.setProfile(DS.PROFILES[i].id, host);
+ },
+ });
+ live.appendChild(host);
+ }
+ mx.appendChild(live);
+
+ SCENES.forEach((n) => {
+ const c = h("div", "mx-cell mx-punch");
+ paintPunch(c, cellOf(n, r));
+ c.onclick = () => {
+ const sets = DS.SCENES[n].sets;
+ const v = cellOf(n, r);
+ let nv;
+ if (r.kind === "power") {
+ nv = PWR_CYCLE[(PWR_CYCLE.indexOf(v) + 1) % PWR_CYCLE.length];
+ if (nv === null) delete sets.powerprofile; else sets.powerprofile = LETTER_PWR[nv];
+ } else {
+ nv = v === true ? false : v === false ? null : true;
+ if (nv === null) delete sets[r.key]; else sets[r.key] = nv;
+ }
+ paintPunch(c, nv);
+ DS.toast(n + " program updated", true);
+ };
+ mx.appendChild(c);
+ });
+ });
+
+ plate.appendChild(mx);
+ col.appendChild(plate);
+
+ // ---- plate 2: levels (horizontal) + caffeine ----------------------------
+ const lv = h("div", "plate");
+ lv.appendChild(DS.engrave("Levels · Caffeine"));
+ [["brightness", "SCREEN"], ["kbd", "KBD"]].forEach(([key, name]) => {
+ const row = h("div", "lvrow");
+ row.appendChild(h("span", "lv-name", name));
+ const host = h("div", "khost");
+ host.style.flex = "1";
+ const commit = debounce((v) => {
+ const pct = Math.max(DS.SLIDERS[key].floor, v);
+ if (pct !== DS.state[key]) DS.setSlider(key, pct, row);
+ }, 350);
+ DUPRE.thumbSlideH(host, {
+ width: 250,
+ value: DS.state[key],
+ onChange: (v) => { if (!booting) commit(v); },
+ });
+ row.appendChild(host);
+ lv.appendChild(row);
+ });
+
+ const trow = h("div", "timer-row");
+ const tcell = h("div", "vcell tcell");
+ if (!timerHost) {
+ timerHost = h("div", "khost");
+ DUPRE.timerDial(timerHost, {
+ minutes: caffMinutes,
+ onChange: (min) => {
+ caffMinutes = min;
+ syncCaffeineSoon();
+ },
+ });
+ scaleSvg(timerHost, 130);
+ tameTimer(timerHost);
+ }
+ tcell.appendChild(timerHost);
+ const batHost = h("div", "bat-oly");
+ DUPRE.batToggle(batHost, {
+ on: !caffHold,
+ onLabel: "TIMED",
+ offLabel: "HOLD",
+ onChange: (on) => {
+ if (booting) return;
+ caffHold = !on;
+ syncCaffeine(batHost);
+ },
+ });
+ tcell.appendChild(batHost);
+ trow.appendChild(tcell);
+ lv.appendChild(trow);
+ col.appendChild(lv);
+
+ // ---- plate 3: pointing devices (never scene-touched) --------------------
+ const pt = h("div", "plate");
+ pt.appendChild(DS.engrave("Pointing"));
+ const strip = h("div", "pointing");
+ [["touchpad", "TPAD"], ["mouse", "MOUSE"]].forEach(([k, name]) => {
+ const host = h("div", "khost");
+ DUPRE.rocker(host, {
+ on: DS.state[k],
+ onLabel: name,
+ offLabel: "",
+ quiet: true,
+ accent: "green",
+ onChange: (on) => { if (!booting && on !== DS.state[k]) DS.setToggle(k, host); },
+ });
+ strip.appendChild(host);
+ });
+ pt.appendChild(strip);
+ col.appendChild(pt);
+
+ // ---- wallpaper ----------------------------------------------------------
+ const wp = h("div", "wprow");
+ wp.innerHTML = `<span>\u{f021c}</span><span>Wallpaper</span><span class="k-state">manage ›</span>`;
+ wp.onclick = openWallpaper;
+ col.appendChild(wp);
+
+ body.appendChild(col);
+ document.getElementById("scene-sub").textContent = DS.state.scene ? "▶ " + DS.state.scene : "";
+ booting = false;
+}
+
+DS.onChange(render);
+render();
+</script>
+</body>
+</html>