aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-30.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-30.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-30.html')
-rw-r--r--docs/prototypes/2026-07-02-desktop-settings-panel-prototype-30.html310
1 files changed, 310 insertions, 0 deletions
diff --git a/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-30.html b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-30.html
new file mode 100644
index 0000000..5081165
--- /dev/null
+++ b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-30.html
@@ -0,0 +1,310 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Desktop Settings — Prototype 30 (family chrome)</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:#dab53d; --gold-hi:#ffd75f;
+ --amber-grad-top:#f0d879; --amber-grad-mid:#dab53d; --amber-grad-bot:#8f7a1f;
+ --amber-edge:#7d6a16; --amber-warn:#edc95d;
+ --glow-hi:255,215,95; --glow-lo:218,181,61;
+ --mono:"BerkeleyMono Nerd Font","Berkeley Mono",monospace;
+ --pulse-rate:1s;
+ }
+ body.ds-stage {
+ background: radial-gradient(1200px 600px at 70% -10%, #1c1915 0%, transparent 60%), var(--ground);
+ }
+ .ds-panel { width: 540px; background-color: var(--panel); border-width: 1.6px; }
+ .ds-face {
+ background: linear-gradient(180deg, var(--raise), var(--panel));
+ border: 1px solid #262320;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.04);
+ }
+ .ds-face .title { color: var(--cream); letter-spacing: .22em; }
+ .ds-caption { max-width: 520px; }
+ .bench { display: flex; align-items: center; justify-content: center; gap: 8px; }
+ .groove { width: 2px; align-self: stretch; margin: 4px 0;
+ background: linear-gradient(180deg, transparent, #060505 15%, #060505 85%, transparent);
+ box-shadow: 1px 0 0 #2a2724; }
+ .rockstack { display: flex; flex-direction: column; gap: 10px; align-items: center; }
+ .rockstack .dupre-rocker { width: 50px; height: 32px; }
+ .rockstack .dupre-rocker .dupre-half { font-size: 8px; }
+ .plate {
+ background: linear-gradient(180deg, var(--raise), var(--panel));
+ border: 1px solid #2c2823;
+ border-radius: 11px;
+ padding: 10px;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.05), 0 2px 6px rgba(0,0,0,.35);
+ }
+ .plate .ds-engrave { margin: 0 2px 8px; }
+ .vcell { display: flex; flex-direction: column; align-items: center; }
+ .khost { display: flex; justify-content: center; }
+ .midgrid { display: flex; gap: 8px; align-items: center; justify-content: center; }
+ .tcell { position: relative; }
+ .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 30 — family chrome.</b> Aligned with the maintenance /
+ net / bt family: theme gold (#dab53d), raised gradient faceplates with
+ inset highlights, maint-style header. Slots labeled 1 · 2 (slim keys);
+ factory presets programmed; PROGRAMMABLE PRESETS on its own
+ high-contrast plate, no divider; dial + drums enlarged, dial numerals
+ lighter.
+ </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);
+const SWITCH_ROWS = [["AUTO-DIM", "autodim"], ["NIGHT LIGHT", "nightlight"], ["DO NOT DISTURB", "dnd"], ["CAFFEINE", "caffeine"]];
+const PWR_ROW = "CPU POWER";
+const PWR_VALUES = ["\u2191", "\u2013", "\u2193"]; // up / dash / down, ordered with DS.PROFILES
+const LETTER_OF = { performance: "\u2191", balanced: "\u2013", saver: "\u2193" };
+const PWR_OF = { "\u2191": "performance", "\u2013": "balanced", "\u2193": "saver" };
+let booting = true;
+// the idle policy the rail programs (mock persistence across renders)
+const IDLE_STAGES = [
+ { id: "dim", label: "DIM", color: "#d29638", minutes: 5 },
+ { id: "lock", label: "LOCK · WATCH", color: "#54677d", minutes: 7 },
+ { id: "dpms", label: "SCREEN OFF", color: "#969385", minutes: 10 },
+ { id: "suspend", label: "SUSPEND", color: "#cb6b4d", minutes: 30 },
+];
+
+// two unassigned program slots (blank keys, all-off programs)
+if (!DS.SCENES.Slot5) DS.SCENES.Slot5 = { glyph: "", sets: {} };
+if (!DS.SCENES.Slot6) DS.SCENES.Slot6 = { glyph: "", sets: {} };
+
+// factory presets (Craig, 2026-07-22) — rows: AUTO-DIM, NIGHT LIGHT, DND, CAFFEINE + CPU
+DS.SCENES.Focus.sets = { autodim: true, nightlight: false, dnd: true, caffeine: true, powerprofile: "performance" };
+DS.SCENES.Presentation.sets = { autodim: false, nightlight: false, dnd: true, caffeine: true, powerprofile: "balanced" };
+DS.SCENES.Battery.sets = { autodim: true, nightlight: false, dnd: false, caffeine: false, powerprofile: "saver" };
+DS.SCENES.Night.sets = { autodim: true, nightlight: true, dnd: false, caffeine: true, powerprofile: "saver" };
+
+// normalize scene programs to COMPLETE definitions: recall = the column's
+// pin pattern becomes the truth (pin on, no pin off; one power pin each).
+// Iterate the live key set so the just-registered blank slots normalize too.
+Object.keys(DS.SCENES).forEach((n) => {
+ const sets = DS.SCENES[n].sets;
+ SWITCH_ROWS.forEach(([, key]) => { sets[key] = !!sets[key]; });
+ if (!sets.powerprofile) sets.powerprofile = "balanced";
+});
+
+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 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));
+}
+
+
+// current pin set for the whole matrix
+function currentPins() {
+ const pins = [];
+ SWITCH_ROWS.forEach(([lbl, key]) => {
+ if (DS.state[key]) pins.push(lbl + ">NOW");
+ SCENES.forEach((n) => { if (DS.SCENES[n].sets[key]) pins.push(lbl + ">" + n); });
+ });
+ PWR_ROWS.forEach(([lbl, id]) => {
+ if (DS.state.powerprofile === id) pins.push(lbl + ">NOW");
+ SCENES.forEach((n) => { if (DS.SCENES[n].sets.powerprofile === id) pins.push(lbl + ">" + n); });
+ });
+ return pins;
+}
+
+function render() {
+ booting = true;
+ body.innerHTML = "";
+ const col = h("div", "ds-col");
+
+ // ---- plate 1: the routing matrix ---------------------------------------
+ const plate = h("div", "plate");
+ plate.appendChild(DS.engrave("Programmable presets"));
+ const mxHost = h("div", "khost");
+ const PROG_DEFS = [
+ { id: "FOCUS", label: "FOCUS", scene: "Focus" },
+ { id: "PRESENT", label: "PRESENT", scene: "Presentation" },
+ { id: "BATTERY", label: "BATTERY", scene: "Battery" },
+ { id: "NIGHT", label: "NIGHT", scene: "Night" },
+ { id: "S5", label: "1", scene: "Slot5", slim: true },
+ { id: "S6", label: "2", scene: "Slot6", slim: true },
+ ];
+ const SCENE_OF = Object.fromEntries(PROG_DEFS.map((p) => [p.id, p.scene]));
+ const PROGS = PROG_DEFS.map(({ id, label, slim }) => ({ id, label, slim }));
+ const pins = [];
+ SWITCH_ROWS.forEach(([lbl, key]) => {
+ PROG_DEFS.forEach((p) => { if (DS.SCENES[p.scene].sets[key]) pins.push(lbl + ">" + p.id); });
+ });
+ const letters = {};
+ PROG_DEFS.forEach((p) => { letters[PWR_ROW + ">" + p.id] = LETTER_OF[DS.SCENES[p.scene].sets.powerprofile]; });
+ const stayActive = (scene) => { DS.state.scene = scene; render(); };
+ const mx = DUPRE.programMatrix(mxHost, {
+ rows: [...SWITCH_ROWS.map(([l]) => l), { label: PWR_ROW, values: PWR_VALUES }],
+ programs: PROGS,
+ pins,
+ letters,
+ active: DS.state.scene ? PROG_DEFS.findIndex((p) => p.scene === DS.state.scene) : -1,
+ width: 486,
+ x0: 128,
+ dy: 23,
+ engraved: true,
+ onActivate: (j) => {
+ if (booting) return;
+ DS.applyScene(PROG_DEFS[j].scene, mxHost);
+ },
+ onPin: (rowLbl, progId, seated, isActive) => {
+ if (booting) return;
+ const scene = SCENE_OF[progId];
+ const sets = DS.SCENES[scene].sets;
+ const sw = SWITCH_ROWS.find(([l]) => l === rowLbl);
+ if (!sw) return;
+ sets[sw[1]] = seated;
+ if (isActive && seated !== DS.state[sw[1]]) {
+ DS.setToggle(sw[1], mxHost);
+ stayActive(scene);
+ } else DS.toast((PROG_DEFS.find((p) => p.id === progId).label || "slot") + " program updated", true);
+ },
+ onLetter: (rowLbl, progId, v, isActive) => {
+ if (booting) return;
+ const scene = SCENE_OF[progId];
+ DS.SCENES[scene].sets.powerprofile = PWR_OF[v];
+ if (isActive && DS.state.powerprofile !== PWR_OF[v]) {
+ DS.setProfile(PWR_OF[v], mxHost);
+ stayActive(scene);
+ } else DS.toast((PROG_DEFS.find((p) => p.id === progId).label || "slot") + " program updated", true);
+ },
+ });
+ plate.appendChild(mxHost);
+ col.appendChild(plate);
+
+ // ---- plate 2: the bench — drums · groove · pointing · groove · dial ----
+ const lv = h("div", "plate");
+ lv.appendChild(DS.engrave("Levels · Pointing · Idle"));
+ const bench = h("div", "bench");
+
+ const dcell = h("div", "vcell");
+ const dhost = h("div", "khost");
+ const commitDrums = debounce((vals) => {
+ const b = Math.max(DS.SLIDERS.brightness.floor, Math.round(vals[0]) * 10);
+ const k = Math.round(vals[1]) * 10;
+ if (b !== DS.state.brightness) DS.setSlider("brightness", b, dcell);
+ if (k !== DS.state.kbd) DS.setSlider("kbd", k, dcell);
+ }, 350);
+ DUPRE.drumRoller(dhost, {
+ title: "LEVELS ×10",
+ channels: [
+ { name: "SCREEN", v: Math.max(1, Math.round(DS.state.brightness / 10)) },
+ { name: "KBD", v: Math.round(DS.state.kbd / 10) },
+ ],
+ min: 0, max: 10,
+ height: 176,
+ onChange: (vals) => { if (!booting) commitDrums(vals.slice()); },
+ });
+ dcell.appendChild(dhost);
+ bench.appendChild(dcell);
+
+ bench.appendChild(h("div", "groove"));
+
+ const rocks = h("div", "rockstack");
+ [["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); },
+ });
+ rocks.appendChild(host);
+ });
+ bench.appendChild(rocks);
+
+ bench.appendChild(h("div", "groove"));
+
+ const icell = h("div", "vcell");
+ const railHost = h("div", "khost");
+ const rail = DUPRE.tripDial(railHost, {
+ stages: IDLE_STAGES.map((st) => ({ ...st })),
+ max: 60,
+ size: 148,
+ legend: true,
+ onChange: (id, minutes, snapshot) => {
+ snapshot.forEach((st) => { const m = IDLE_STAGES.find((o) => o.id === st.id); if (m) m.minutes = st.minutes; });
+ if (booting || !id) return;
+ const st = IDLE_STAGES.find((o) => o.id === id);
+ DS.toast(st.label + (minutes == null ? " stage disabled" : " → idle " + Math.round(minutes) + "m"), true);
+ },
+ });
+ rail.setBypassed(DS.state.caffeine);
+ icell.appendChild(railHost);
+ bench.appendChild(icell);
+
+ lv.appendChild(bench);
+ col.appendChild(lv);
+
+ // ---- 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>