aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-21.html
diff options
context:
space:
mode:
Diffstat (limited to 'docs/prototypes/2026-07-02-desktop-settings-panel-prototype-21.html')
-rw-r--r--docs/prototypes/2026-07-02-desktop-settings-panel-prototype-21.html287
1 files changed, 287 insertions, 0 deletions
diff --git a/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-21.html b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-21.html
new file mode 100644
index 0000000..274ae78
--- /dev/null
+++ b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-21.html
@@ -0,0 +1,287 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Desktop Settings — Prototype 21 (pin routing 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; }
+ .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 21 — the pin routing matrix.</b> The interface is the
+ matrix: switch names label the rows, the program buttons head the
+ columns, pins route settings into programs. The NOW column is live —
+ green pins are current state, click to toggle. Power is a radio row
+ group (one pin per column). CAFF is an ordinary switch; the dial is
+ now a SLEEP TIMER (wind minutes until suspend). The drums are back.
+ </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 = [["DIM", "autodim"], ["NIGHT", "nightlight"], ["DND", "dnd"], ["CAFF", "caffeine"]];
+const PWR_ROWS = [["PERF", "performance"], ["BAL", "balanced"], ["SAVER", "saver"]];
+const ROW_LABELS = [...SWITCH_ROWS.map(r => r[0]), ...PWR_ROWS.map(r => r[0])];
+const COLS = ["NOW", ...SCENES];
+let booting = true;
+let sleepMinutes = 0;
+let timerHost = null; // persistent: the timer owns a live countdown
+
+// normalize scene programs to COMPLETE definitions: recall = the column's
+// pin pattern becomes the truth (pin on, no pin off; one power pin each)
+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));
+}
+
+// the dial is a SLEEP TIMER now: wind minutes; zero = suspend (mocked)
+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="p21Steel" 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(#p21Steel)"));
+ [...svg.querySelectorAll("text")].forEach((t) => {
+ if (t.textContent === "TURN TO START") t.remove();
+ if (t.textContent === "PUSH TO STOP") t.textContent = "SLEEP TIMER · PUSH STOPS";
+ if (t.textContent === "MINUTES") t.textContent = "SLEEP IN";
+ 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();
+}
+
+// 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("Program routing"));
+ const mxHost = h("div", "khost");
+ const mx = DUPRE.pinMatrix(mxHost, {
+ rows: ROW_LABELS,
+ cols: COLS,
+ pins: currentPins(),
+ width: 306,
+ x0: 58,
+ dy: 17,
+ colHeads: "button",
+ activeCol: DS.state.scene ? SCENES.indexOf(DS.state.scene) + 1 : -1,
+ colColors: ["#a9c95f", "#cfd3cf", "#cfd3cf", "#cfd3cf", "#cfd3cf"],
+ groups: [SWITCH_ROWS.length, PWR_ROWS.length],
+ onColHead: (j) => {
+ if (booting) return;
+ if (j === 0) return; // NOW isn't a program
+ DS.applyScene(SCENES[j - 1], mxHost);
+ },
+ onPin: (rowLbl, colLbl, seated) => {
+ if (booting) return;
+ const sw = SWITCH_ROWS.find(([l]) => l === rowLbl);
+ const pw = PWR_ROWS.find(([l]) => l === rowLbl);
+ if (colLbl === "NOW") {
+ if (sw) {
+ if (seated !== DS.state[sw[1]]) DS.setToggle(sw[1], mxHost);
+ else render();
+ } else if (pw) {
+ if (!seated) { mx.set(rowLbl + ">NOW", true); return; } // can't pull the live profile
+ DS.setProfile(pw[1], mxHost);
+ }
+ return;
+ }
+ const sets = DS.SCENES[colLbl].sets;
+ if (sw) {
+ sets[sw[1]] = seated;
+ DS.toast(colLbl + " program updated", true);
+ } else if (pw) {
+ if (!seated) { mx.set(rowLbl + ">" + colLbl, true); return; } // one power pin per program
+ sets.powerprofile = pw[1];
+ PWR_ROWS.forEach(([l, id]) => { if (id !== pw[1]) mx.set(l + ">" + colLbl, false); });
+ DS.toast(colLbl + " program updated", true);
+ }
+ },
+ });
+ plate.appendChild(mxHost);
+ col.appendChild(plate);
+
+ // ---- plate 2: levels (the drums are back) + sleep timer -----------------
+ const lv = h("div", "plate");
+ lv.appendChild(DS.engrave("Levels · Sleep"));
+ const mid = h("div", "midgrid");
+
+ 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: 132,
+ onChange: (vals) => { if (!booting) commitDrums(vals.slice()); },
+ });
+ dcell.appendChild(dhost);
+ mid.appendChild(dcell);
+
+ const tcell = h("div", "vcell tcell");
+ if (!timerHost) {
+ timerHost = h("div", "khost");
+ DUPRE.timerDial(timerHost, {
+ minutes: 0,
+ onChange: (min) => {
+ const was = sleepMinutes;
+ sleepMinutes = min;
+ if (booting) return;
+ if (was > 0.5 && min === 0) DS.toast("Sleep timer expired — SUSPEND (mock)", true);
+ },
+ });
+ scaleSvg(timerHost, 132);
+ tameTimer(timerHost);
+ }
+ tcell.appendChild(timerHost);
+ mid.appendChild(tcell);
+ lv.appendChild(mid);
+ col.appendChild(lv);
+
+ // ---- plate 3: pointing (never program-routed) ---------------------------
+ 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>