aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-8.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-8.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-8.html')
-rw-r--r--docs/prototypes/2026-07-02-desktop-settings-panel-prototype-8.html360
1 files changed, 360 insertions, 0 deletions
diff --git a/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-8.html b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-8.html
new file mode 100644
index 0000000..13fed0b
--- /dev/null
+++ b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-8.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 8 (gauge cluster)</title>
+<link rel="stylesheet" href="desktop-settings-shared.css">
+<style>
+ /* 60s dashboard: twin needle gauges in a binnacle, a rocker-switch bank,
+ push-button drive modes for scenes, an ignition rotary for power. */
+ .binnacle {
+ background:
+ radial-gradient(140px 90px at 25% 20%, #ffffff07, transparent),
+ linear-gradient(180deg, #1c1a17, #121110);
+ border: 1px solid var(--plate-edge);
+ border-radius: 14px;
+ padding: 12px 10px 8px;
+ display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
+ }
+ .gauge {
+ position: relative;
+ width: 100%; aspect-ratio: 1;
+ border-radius: 50%;
+ background: radial-gradient(circle at 38% 30%, #16140f, #0a0908 78%);
+ border: 3px solid #2c2820;
+ box-shadow: inset 0 0 16px #000c, 0 4px 12px #0009;
+ cursor: pointer;
+ touch-action: none;
+ }
+ .gauge .tk {
+ position: absolute; left: 50%; top: 8px; width: 1.5px; height: 7px;
+ margin-left: -0.75px;
+ background: #7a7057;
+ transform-origin: 50% calc(var(--gr) * 1px);
+ pointer-events: none;
+ }
+ .gauge .tk.mj { width: 2.5px; height: 11px; background: var(--cream); }
+ .gauge .num {
+ position: absolute; left: 50%; top: 50%;
+ color: var(--steel); font-size: 9px;
+ transform: translate(-50%, -50%);
+ pointer-events: none;
+ }
+ .gauge .needle {
+ position: absolute; left: 50%; bottom: 50%; width: 3px; height: 42%;
+ margin-left: -1.5px;
+ background: linear-gradient(180deg, var(--fail) 60%, #7e4530);
+ transform-origin: 50% 100%;
+ border-radius: 3px 3px 0 0;
+ box-shadow: 0 0 6px #cb6b4d66;
+ transition: transform .18s ease-out;
+ pointer-events: none;
+ }
+ .gauge .hub {
+ position: absolute; left: 50%; top: 50%; width: 14px; height: 14px;
+ margin: -7px 0 0 -7px; border-radius: 50%;
+ background: radial-gradient(circle at 35% 30%, #4a453b, #211e19);
+ border: 1px solid #383328;
+ pointer-events: none;
+ }
+ .gauge .g-lbl {
+ position: absolute; width: 100%; text-align: center; top: 27%;
+ color: var(--steel); font-size: 8px; letter-spacing: 0.2em;
+ pointer-events: none;
+ }
+ .odo {
+ position: absolute; width: 100%; text-align: center; bottom: 17%;
+ pointer-events: none;
+ }
+ .odo span {
+ display: inline-block;
+ background: #060505; border: 1px solid #2a2620; border-radius: 3px;
+ color: var(--cream); font-size: 11px; font-variant-numeric: tabular-nums;
+ padding: 1px 6px; letter-spacing: 0.12em;
+ }
+
+ /* Rocker bank: tilting switches with engraved legends. */
+ .rockers { display: grid; grid-template-columns: repeat(4,1fr); gap: 6px; }
+ .rocker {
+ display: flex; flex-direction: column; align-items: center; gap: 4px;
+ cursor: pointer; padding: 5px 2px 4px;
+ border-radius: 6px; border: 1px solid transparent;
+ }
+ .rocker:hover { border-color: var(--key-edge); }
+ .rk {
+ width: 26px; height: 38px; border-radius: 5px;
+ background: linear-gradient(180deg, var(--key-top), var(--key-bot));
+ border: 1px solid var(--key-edge);
+ position: relative;
+ perspective: 60px;
+ }
+ .rk .cap {
+ position: absolute; inset: 2px; border-radius: 3px;
+ background: linear-gradient(180deg, #2e2b25 48%, #191712 52%);
+ transform: rotateX(14deg);
+ transition: transform .12s, background .12s;
+ }
+ .rocker.on .rk { border-color: var(--gold); }
+ .rocker.on .rk .cap {
+ transform: rotateX(-14deg);
+ background: linear-gradient(180deg, #191712 48%, #4a3d16 52%);
+ }
+ .rocker .lgd {
+ font-size: 7.5px; letter-spacing: 0.06em; color: var(--dim);
+ text-transform: uppercase; text-align: center;
+ }
+ .rocker.on .lgd { color: var(--gold-bright); text-shadow: 0 0 6px #dab53d88; }
+ .rocker .g { font-size: 13px; color: var(--steel); }
+ .rocker.on .g { color: var(--gold); }
+ .rocker.verified { animation: dsVerify 0.9s ease-out; }
+
+ /* Drive-mode push buttons (scenes): the push-button transmission column. */
+ .modes { display: grid; grid-template-columns: repeat(4,1fr); gap: 6px; }
+ .mode {
+ background: linear-gradient(180deg, var(--key-top), var(--key-bot));
+ border: 1px solid var(--key-edge);
+ border-bottom-width: 4px;
+ border-radius: 6px;
+ padding: 7px 2px;
+ text-align: center; cursor: pointer;
+ }
+ .mode .ltr { display: block; color: var(--cream); font-size: 15px; font-weight: 700; }
+ .mode .nm { display: block; color: var(--dim); font-size: 7.5px; letter-spacing: 0.08em; text-transform: uppercase; margin-top: 2px; }
+ .mode:hover { border-color: var(--gold); }
+ .mode.cur {
+ border-color: var(--gold); border-bottom-width: 1px;
+ transform: translateY(3px);
+ background: linear-gradient(180deg, #2a2618, #1d1a12);
+ }
+ .mode.cur .ltr { color: var(--gold-bright); text-shadow: 0 0 8px #dab53d99; }
+ .mode.cur .nm { color: var(--gold); }
+
+ /* Ignition rotary (power profile). */
+ .ignition { display: flex; align-items: center; gap: 14px; padding: 2px 6px; }
+ .ig-knob {
+ width: 54px; height: 54px; border-radius: 50%;
+ background: radial-gradient(circle at 35% 30%, #35322c, #191715 70%);
+ border: 2px solid var(--key-edge);
+ box-shadow: 0 3px 8px #000a;
+ position: relative; cursor: pointer;
+ }
+ .ig-knob .slot {
+ position: absolute; left: 50%; top: 50%; width: 5px; height: 26px;
+ margin: -13px 0 0 -2.5px;
+ background: #0a0908; border-radius: 3px; border: 1px solid #3a352c;
+ transform-origin: 50% 50%;
+ transition: transform .2s;
+ }
+ .ig-labels { display: flex; flex-direction: column; gap: 3px; }
+ .ig-labels .det { color: var(--dim); font-size: 10px; letter-spacing: 0.12em; cursor: pointer; }
+ .ig-labels .det:hover { color: var(--silver); }
+ .ig-labels .det.cur { color: var(--gold-bright); text-shadow: 0 0 7px #dab53d77; }
+ .ig-lbl { margin-left: auto; color: var(--steel); font-size: 9px; letter-spacing: 0.16em; }
+
+ /* Dash pull-knob actions. */
+ .pulls { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; }
+ .pull {
+ display: flex; flex-direction: column; align-items: center; gap: 4px;
+ cursor: pointer; padding: 5px 2px;
+ border-radius: 6px; border: 1px solid transparent;
+ }
+ .pull:hover { border-color: var(--key-edge); }
+ .pull .cap {
+ width: 30px; height: 30px; border-radius: 50%;
+ background: radial-gradient(circle at 35% 30%, #3a362e, #1c1a15);
+ border: 2px solid var(--key-edge);
+ box-shadow: 0 3px 6px #000a;
+ display: flex; align-items: center; justify-content: center;
+ color: var(--steel); font-size: 13px;
+ }
+ .pull:hover .cap { border-color: var(--gold); color: var(--gold); }
+ .pull .lgd { font-size: 8px; letter-spacing: 0.1em; color: var(--dim); text-transform: uppercase; }
+</style>
+</head>
+<body class="ds-stage">
+ <div class="ds-caption">
+ <b>Direction 8 — Gauge cluster.</b> A dashboard binnacle: brightness and
+ keyboard are needle gauges (drag across the dial), toggles are a rocker
+ bank with backlit legends, scenes are push-button drive modes, power is an
+ ignition rotary, actions are dash pull-knobs.
+ </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="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 SCENE_ORDER = Object.keys(DS.SCENES);
+const SWEEP = 240, START = -120; // gauge sweep
+
+function openWallpaper() {
+ subview.innerHTML = "";
+ subview.appendChild(DS.wallpaperSubview(() => subview.classList.remove("open")));
+ subview.classList.add("open");
+}
+
+function valueAt(e, el, floor) {
+ const r = el.getBoundingClientRect();
+ const dx = e.clientX - (r.left + r.width / 2);
+ const dy = e.clientY - (r.top + r.height / 2);
+ let a = Math.atan2(dx, -dy) * 180 / Math.PI;
+ a = Math.max(START, Math.min(START + SWEEP, a));
+ return Math.round(floor + ((a - START) / SWEEP) * (100 - floor));
+}
+
+function gauge(key, label) {
+ const meta = DS.SLIDERS[key];
+ const g = h("div", "gauge");
+
+ // ticks + numerals — radius derives from the rendered size after mount
+ requestAnimationFrame(() => {
+ const R = g.clientWidth / 2;
+ for (let i = 0; i <= 10; i++) {
+ const t = h("div", "tk" + (i % 2 === 0 ? " mj" : ""));
+ t.style.setProperty("--gr", R - 8);
+ t.style.transform = `rotate(${START + (i / 10) * SWEEP}deg)`;
+ g.appendChild(t);
+ if (i % 2 === 0) {
+ const a = (START + (i / 10) * SWEEP - 90) * Math.PI / 180;
+ const nr = R - 26;
+ const n = h("div", "num", i * 10);
+ n.style.left = 50 + (Math.cos(a) * nr / R) * 50 + "%";
+ n.style.top = 50 + (Math.sin(a) * nr / R) * 50 + "%";
+ g.appendChild(n);
+ }
+ }
+ });
+
+ g.appendChild(h("div", "g-lbl", label));
+ const nd = h("div", "needle");
+ const odo = h("div", "odo");
+ const odoSpan = h("span");
+ odo.appendChild(odoSpan);
+ const paint = (v) => {
+ nd.style.transform = `rotate(${START + ((v - meta.floor) / (100 - meta.floor)) * SWEEP}deg)`;
+ odoSpan.textContent = String(v).padStart(3, "0");
+ };
+ paint(DS.state[key]);
+ g.appendChild(nd);
+ g.appendChild(h("div", "hub"));
+ g.appendChild(odo);
+
+ let live = DS.state[key];
+ g.onpointerdown = (e) => {
+ g.setPointerCapture(e.pointerId);
+ live = valueAt(e, g, meta.floor); paint(live);
+ g.onpointermove = (m) => { live = valueAt(m, g, meta.floor); paint(live); };
+ g.onpointerup = () => { g.onpointermove = null; DS.setSlider(key, live, g); };
+ };
+ return g;
+}
+
+function rockerBank() {
+ const bank = h("div", "rockers");
+ const keys = ["autodim", "caffeine", "touchpad", "mouse", "nightlight", "dnd", "weathergeo"];
+ if (DS.state.laptop) keys.push("airplane");
+ keys.forEach((k) => {
+ const meta = DS.TOGGLES[k];
+ const el = h("div", "rocker" + (DS.state[k] ? " on" : ""));
+ el.innerHTML =
+ `<span class="g">${meta.glyph}</span>` +
+ `<span class="rk"><span class="cap"></span></span>` +
+ `<span class="lgd">${meta.name}</span>`;
+ el.onclick = () => DS.setToggle(k, el);
+ bank.appendChild(el);
+ });
+ return bank;
+}
+
+function driveModes() {
+ const m = h("div", "modes");
+ SCENE_ORDER.forEach((n) => {
+ const el = h("div", "mode" + (DS.state.scene === n ? " cur" : ""));
+ el.innerHTML = `<span class="ltr">${n[0]}</span><span class="nm">${n}</span>`;
+ el.onclick = () => DS.applyScene(n, el);
+ m.appendChild(el);
+ });
+ return m;
+}
+
+function ignition() {
+ const ig = h("div", "ignition");
+ const kn = h("div", "ig-knob");
+ const slot = h("div", "slot");
+ const idx = DS.PROFILES.findIndex((p) => p.id === DS.state.powerprofile);
+ slot.style.transform = `rotate(${idx * 45 - 45}deg)`;
+ kn.appendChild(slot);
+ kn.onclick = () => DS.setProfile(DS.PROFILES[(idx + 1) % DS.PROFILES.length].id, ig);
+ ig.appendChild(kn);
+ const labels = h("div", "ig-labels");
+ DS.PROFILES.forEach((p) => {
+ const d = h("span", "det" + (p.id === DS.state.powerprofile ? " cur" : ""), p.label.toUpperCase());
+ d.onclick = () => DS.setProfile(p.id, ig);
+ labels.appendChild(d);
+ });
+ ig.appendChild(labels);
+ ig.appendChild(h("span", "ig-lbl", "IGNITION"));
+ return ig;
+}
+
+function pulls() {
+ const p = h("div", "pulls");
+ const mk = (glyph, label, fn) => {
+ const el = h("div", "pull");
+ el.innerHTML = `<span class="cap">${glyph}</span><span class="lgd">${label}</span>`;
+ el.onclick = fn || (() => DS.momentary(label));
+ return el;
+ };
+ p.appendChild(mk("\u{f033e}", "Lock"));
+ p.appendChild(mk("\u{f04b2}", "Suspend"));
+ p.appendChild(mk("\u{f021c}", "Wallpaper", openWallpaper));
+ return p;
+}
+
+function render() {
+ body.innerHTML = "";
+ const col = h("div", "ds-col");
+
+ const bn = h("div", "binnacle");
+ bn.appendChild(gauge("brightness", "BRIGHTNESS"));
+ bn.appendChild(gauge("kbd", "KEYBOARD"));
+ col.appendChild(bn);
+
+ col.appendChild(DS.engrave("Switches"));
+ col.appendChild(rockerBank());
+
+ col.appendChild(DS.engrave("Drive modes"));
+ col.appendChild(driveModes());
+
+ col.appendChild(DS.engrave("Ignition"));
+ col.appendChild(ignition());
+
+ col.appendChild(DS.engrave("Actions"));
+ col.appendChild(pulls());
+
+ body.appendChild(col);
+ document.getElementById("scene-sub").textContent = DS.state.scene ? "▶ " + DS.state.scene : "";
+}
+
+DS.onChange(render);
+render();
+</script>
+</body>
+</html>