aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-6.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-6.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-6.html')
-rw-r--r--docs/prototypes/2026-07-02-desktop-settings-panel-prototype-6.html343
1 files changed, 343 insertions, 0 deletions
diff --git a/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-6.html b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-6.html
new file mode 100644
index 0000000..952e4d9
--- /dev/null
+++ b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-6.html
@@ -0,0 +1,343 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Desktop Settings — Prototype 6 (hi-fi receiver)</title>
+<link rel="stylesheet" href="desktop-settings-shared.css">
+<style>
+ /* Hi-fi receiver faceplate: brushed plate, backlit tuner dial with scene
+ "stations", preset keys, VU meters over rotary knobs, switch bank. */
+ .plate {
+ background:
+ repeating-linear-gradient(90deg, #ffffff05 0 1px, transparent 1px 3px),
+ linear-gradient(180deg, #201e1a, #171512);
+ border: 1px solid var(--plate-edge);
+ border-radius: 10px;
+ padding: 10px;
+ }
+
+ /* Tuner dial: warm backlit window, station labels, sliding needle. */
+ .tuner {
+ position: relative;
+ height: 64px;
+ border-radius: 8px;
+ background: linear-gradient(180deg, #2b2414, #1c1809);
+ border: 1px solid #3a321c;
+ box-shadow: inset 0 0 18px #dab53d22;
+ overflow: hidden;
+ }
+ .tuner .scale {
+ position: absolute; left: 10px; right: 10px; top: 12px; height: 8px;
+ background:
+ repeating-linear-gradient(90deg, #dab53d55 0 1px, transparent 1px 8.5%);
+ border-bottom: 1px solid #dab53d44;
+ }
+ .tuner .station {
+ position: absolute; top: 28px;
+ transform: translateX(-50%);
+ color: #9a8a55; font-size: 10px; letter-spacing: 0.1em;
+ cursor: pointer; padding: 2px 4px;
+ }
+ .tuner .station:hover { color: var(--gold-bright); }
+ .tuner .station.cur { color: var(--gold-bright); text-shadow: 0 0 8px #dab53daa; }
+ .tuner .freq {
+ position: absolute; bottom: 3px; left: 10px;
+ color: #6a5f3a; font-size: 9px; letter-spacing: 0.14em;
+ }
+ .tuner .needle {
+ position: absolute; top: 6px; bottom: 6px; width: 2px;
+ background: linear-gradient(180deg, var(--fail), #8a4830);
+ box-shadow: 0 0 6px #cb6b4d88;
+ transition: left .35s cubic-bezier(.3,1.2,.4,1);
+ }
+
+ /* Preset keys: chunky mechanical station buttons under the dial. */
+ .presets { display: grid; grid-template-columns: repeat(4,1fr); gap: 6px; margin-top: 8px; }
+ .preset {
+ background-image: linear-gradient(180deg, var(--key-top), var(--key-bot));
+ border: 1px solid var(--key-edge);
+ border-bottom-width: 3px;
+ border-radius: 6px;
+ padding: 6px 4px;
+ text-align: center;
+ cursor: pointer;
+ color: var(--silver);
+ }
+ .preset:hover { border-color: var(--gold); color: var(--gold); }
+ .preset .num { font-size: 9px; color: var(--dim); display: block; }
+ .preset .nm { font-size: 10.5px; letter-spacing: 0.06em; }
+ .preset.cur {
+ border-color: var(--gold); border-bottom-width: 1px;
+ background-image: linear-gradient(180deg, #2a2618, #1d1a12);
+ transform: translateY(2px);
+ color: var(--cream);
+ }
+ .preset.cur .num { color: var(--gold); }
+
+ /* VU meter: cream arc window with a swinging needle. */
+ .duo { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
+ .vu {
+ position: relative; height: 58px; border-radius: 8px 8px 4px 4px;
+ background: linear-gradient(180deg, #efe3c2, #d9cba2);
+ border: 1px solid #3a3226;
+ overflow: hidden;
+ }
+ .vu .arc {
+ position: absolute; left: 8px; right: 8px; top: 10px; height: 5px;
+ border-radius: 4px;
+ background: linear-gradient(90deg, #6b6352 70%, #b5432e 70%);
+ opacity: .8;
+ }
+ .vu .vu-lbl {
+ position: absolute; bottom: 3px; width: 100%; text-align: center;
+ color: #5a5140; font-size: 8.5px; letter-spacing: 0.18em;
+ }
+ .vu .nd {
+ position: absolute; left: 50%; bottom: -6px; width: 2px; height: 52px;
+ background: #1d1a14;
+ transform-origin: 50% 100%;
+ transition: transform .25s ease-out;
+ }
+ .knob-cell { display: flex; flex-direction: column; align-items: center; gap: 5px; margin-top: 6px; }
+ .knob {
+ width: 52px; height: 52px; border-radius: 50%;
+ background:
+ radial-gradient(circle at 35% 30%, #35322c, #191715 70%),
+ var(--key-bot);
+ border: 2px solid var(--key-edge);
+ box-shadow: 0 3px 8px #000a, inset 0 1px 0 #ffffff14;
+ position: relative;
+ cursor: ew-resize;
+ touch-action: none;
+ }
+ .knob .ptr {
+ position: absolute; left: 50%; top: 4px; width: 2px; height: 14px;
+ margin-left: -1px;
+ background: var(--gold);
+ transform-origin: 50% 22px;
+ }
+ .knob-lbl { color: var(--steel); font-size: 9.5px; letter-spacing: 0.14em; }
+ .knob-val { color: var(--cream); font-size: 11px; font-variant-numeric: tabular-nums; }
+
+ /* Switch bank: square backlit-legend switches, two rows of four. */
+ .bank { display: grid; grid-template-columns: repeat(4,1fr); gap: 6px; }
+ .sw {
+ background-image: linear-gradient(180deg, var(--key-top), var(--key-bot));
+ border: 1px solid var(--key-edge);
+ border-radius: 6px;
+ padding: 7px 3px 5px;
+ display: flex; flex-direction: column; align-items: center; gap: 4px;
+ cursor: pointer;
+ }
+ .sw:hover { border-color: var(--gold); }
+ .sw .g { font-size: 15px; color: var(--steel); }
+ .sw .lgd { font-size: 8px; letter-spacing: 0.08em; color: var(--dim); text-transform: uppercase; }
+ .sw.on { border-color: var(--gold); background-image: linear-gradient(180deg, #2a2618, #1d1a12); }
+ .sw.on .g { color: var(--gold); }
+ .sw.on .lgd { color: var(--gold-bright); text-shadow: 0 0 7px #dab53d99; }
+ .sw.verified { animation: dsVerify 0.9s ease-out; }
+
+ /* Input selector: one rotary with three engraved detents. */
+ .selector { display: flex; align-items: center; gap: 14px; padding: 2px 6px; }
+ .selector .labels { display: flex; flex-direction: column; gap: 3px; }
+ .selector .det { color: var(--dim); font-size: 10px; letter-spacing: 0.12em; cursor: pointer; }
+ .selector .det:hover { color: var(--silver); }
+ .selector .det.cur { color: var(--gold-bright); text-shadow: 0 0 7px #dab53d77; }
+</style>
+</head>
+<body class="ds-stage">
+ <div class="ds-caption">
+ <b>Direction 6 — Hi-fi receiver.</b> Scenes are station presets on a backlit
+ tuner dial; the needle slides to the tuned scene. Rotary knobs + VU meters
+ for the levels, a backlit switch bank for toggles, an input selector for
+ power. Drag knobs sideways; click stations or preset keys.
+ </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); // 4 stations
+const STATION_POS = [14, 38, 62, 86]; // % across the dial
+
+function openWallpaper() {
+ subview.innerHTML = "";
+ subview.appendChild(DS.wallpaperSubview(() => subview.classList.remove("open")));
+ subview.classList.add("open");
+}
+
+function tuner() {
+ const t = h("div", "tuner");
+ t.appendChild(h("div", "scale"));
+ SCENE_ORDER.forEach((n, i) => {
+ const s = h("span", "station" + (DS.state.scene === n ? " cur" : ""), n.toUpperCase());
+ s.style.left = STATION_POS[i] + "%";
+ s.onclick = () => DS.applyScene(n, t);
+ t.appendChild(s);
+ });
+ t.appendChild(h("span", "freq", "SCENE TUNING"));
+ const nd = h("div", "needle");
+ const idx = SCENE_ORDER.indexOf(DS.state.scene);
+ nd.style.left = (idx < 0 ? 3 : STATION_POS[idx]) + "%";
+ t.appendChild(nd);
+ return t;
+}
+
+function presetKeys() {
+ const wrap = h("div", "presets");
+ SCENE_ORDER.forEach((n, i) => {
+ const k = h("div", "preset" + (DS.state.scene === n ? " cur" : ""));
+ k.innerHTML = `<span class="num">PRESET ${i + 1}</span><span class="nm">${n}</span>`;
+ k.onclick = () => DS.applyScene(n, k);
+ wrap.appendChild(k);
+ });
+ return wrap;
+}
+
+// VU needle: value 0-100 → -42°..42° swing.
+function vuMeter(key, label) {
+ const v = h("div", "vu");
+ v.appendChild(h("div", "arc"));
+ const nd = h("div", "nd");
+ nd.style.transform = `rotate(${(DS.state[key] / 100) * 84 - 42}deg)`;
+ nd.dataset.vu = key;
+ v.appendChild(nd);
+ v.appendChild(h("div", "vu-lbl", label));
+ return v;
+}
+
+// Rotary knob: drag sideways (or wheel) to set; pointer sweeps -135°..135°.
+function knob(key, label) {
+ const meta = DS.SLIDERS[key];
+ const cell = h("div", "knob-cell");
+ const kn = h("div", "knob");
+ const ptr = h("div", "ptr");
+ const val = h("div", "knob-val");
+ const angle = (v) => ((v - meta.floor) / (100 - meta.floor)) * 270 - 135;
+ const paint = (v) => {
+ ptr.style.transform = `rotate(${angle(v)}deg)`;
+ val.textContent = v + "%";
+ const vu = document.querySelector(`[data-vu="${key}"]`);
+ if (vu) vu.style.transform = `rotate(${(v / 100) * 84 - 42}deg)`;
+ };
+ kn.appendChild(ptr);
+ paint(DS.state[key]);
+ let live = DS.state[key];
+ kn.onpointerdown = (e) => {
+ kn.setPointerCapture(e.pointerId);
+ const startX = e.clientX, startV = live;
+ kn.onpointermove = (m) => {
+ live = Math.max(meta.floor, Math.min(100, Math.round(startV + (m.clientX - startX) / 2)));
+ paint(live);
+ };
+ kn.onpointerup = () => { kn.onpointermove = null; DS.setSlider(key, live, kn); };
+ };
+ kn.onwheel = (e) => {
+ e.preventDefault();
+ live = Math.max(meta.floor, Math.min(100, live + (e.deltaY < 0 ? 5 : -5)));
+ paint(live);
+ clearTimeout(kn._w);
+ kn._w = setTimeout(() => DS.setSlider(key, live, kn), 350);
+ };
+ cell.appendChild(kn);
+ cell.appendChild(h("div", "knob-lbl", label));
+ cell.appendChild(val);
+ return cell;
+}
+
+function switchBank() {
+ const bank = h("div", "bank");
+ 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", "sw" + (DS.state[k] ? " on" : ""));
+ el.innerHTML = `<span class="g">${meta.glyph}</span><span class="lgd">${meta.name}</span>`;
+ el.onclick = () => DS.setToggle(k, el);
+ bank.appendChild(el);
+ });
+ return bank;
+}
+
+function inputSelector() {
+ const sel = h("div", "selector");
+ const kn = h("div", "knob");
+ kn.style.cursor = "pointer";
+ const ptr = h("div", "ptr");
+ const idx = DS.PROFILES.findIndex((p) => p.id === DS.state.powerprofile);
+ ptr.style.transform = `rotate(${idx * 45 - 45}deg)`;
+ kn.appendChild(ptr);
+ kn.onclick = () => {
+ const next = DS.PROFILES[(idx + 1) % DS.PROFILES.length];
+ DS.setProfile(next.id, sel);
+ };
+ sel.appendChild(kn);
+ const labels = h("div", "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, sel);
+ labels.appendChild(d);
+ });
+ sel.appendChild(labels);
+ sel.appendChild(h("span", "knob-lbl", "POWER"));
+ return sel;
+}
+
+function render() {
+ body.innerHTML = "";
+ const col = h("div", "ds-col");
+
+ const dialPlate = h("div", "plate");
+ dialPlate.appendChild(tuner());
+ dialPlate.appendChild(presetKeys());
+ col.appendChild(dialPlate);
+
+ col.appendChild(DS.engrave("Levels"));
+ const lv = h("div", "plate");
+ const duo = h("div", "duo");
+ const c1 = h("div"); c1.appendChild(vuMeter("brightness", "BRIGHTNESS")); c1.appendChild(knob("brightness", "BRIGHTNESS"));
+ const c2 = h("div"); c2.appendChild(vuMeter("kbd", "KEYBOARD")); c2.appendChild(knob("kbd", "KEYBOARD"));
+ duo.appendChild(c1); duo.appendChild(c2);
+ lv.appendChild(duo);
+ col.appendChild(lv);
+
+ col.appendChild(DS.engrave("Switches"));
+ col.appendChild(switchBank());
+
+ col.appendChild(DS.engrave("Power"));
+ col.appendChild(inputSelector());
+
+ col.appendChild(DS.engrave("Actions"));
+ const acts = h("div", "ds-grid3");
+ acts.appendChild(DS.actionBtn("Lock", "\u{f033e}", false));
+ acts.appendChild(DS.actionBtn("Suspend", "\u{f04b2}", false));
+ acts.appendChild(DS.actionBtn("Wallpaper", "\u{f021c}", false, openWallpaper));
+ col.appendChild(acts);
+
+ body.appendChild(col);
+ document.getElementById("scene-sub").textContent = DS.state.scene ? "▶ " + DS.state.scene : "";
+}
+
+DS.onChange(render);
+render();
+</script>
+</body>
+</html>