aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-7.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-7.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-7.html')
-rw-r--r--docs/prototypes/2026-07-02-desktop-settings-panel-prototype-7.html322
1 files changed, 322 insertions, 0 deletions
diff --git a/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-7.html b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-7.html
new file mode 100644
index 0000000..dc7ac0a
--- /dev/null
+++ b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-7.html
@@ -0,0 +1,322 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Desktop Settings — Prototype 7 (chronograph)</title>
+<link rel="stylesheet" href="desktop-settings-shared.css">
+<style>
+ /* Chronograph: one big watch face. Brightness is the main dial (drag the
+ bezel), chrono subdials carry keyboard / power / scene, toggles are the
+ bracelet links below. */
+ .case {
+ display: flex; flex-direction: column; align-items: center; gap: 8px;
+ }
+ .face-wrap { position: relative; width: 300px; height: 300px; }
+ .bezel {
+ position: absolute; inset: 0; border-radius: 50%;
+ background:
+ radial-gradient(circle at 32% 28%, #3a362e, #191715 72%);
+ border: 2px solid var(--key-edge);
+ box-shadow: 0 8px 26px #000c, inset 0 1px 0 #ffffff14;
+ cursor: pointer;
+ touch-action: none;
+ }
+ .ring {
+ position: absolute; inset: 12px; border-radius: 50%;
+ }
+ .dial {
+ position: absolute; inset: 34px; border-radius: 50%;
+ background: radial-gradient(circle at 40% 32%, #14130f, #0b0a08 75%);
+ border: 1px solid #2c2820;
+ pointer-events: none;
+ }
+ .tick {
+ position: absolute; left: 50%; top: 14px; width: 1px; height: 7px;
+ margin-left: -0.5px;
+ background: #6a6250;
+ transform-origin: 50% 136px;
+ pointer-events: none;
+ }
+ .tick.major { width: 2px; height: 11px; margin-left: -1px; background: var(--gold); }
+ .hand {
+ position: absolute; left: 50%; top: 44px; width: 3px; height: 106px;
+ margin-left: -1.5px;
+ background: linear-gradient(180deg, var(--gold-bright), var(--gold) 80%, transparent);
+ transform-origin: 50% 106px;
+ border-radius: 3px;
+ box-shadow: 0 0 6px #dab53d55;
+ transition: transform .2s ease-out;
+ pointer-events: none;
+ }
+ .cap {
+ position: absolute; left: 50%; top: 50%; width: 12px; height: 12px;
+ margin: -6px 0 0 -6px; border-radius: 50%;
+ background: radial-gradient(circle at 35% 30%, var(--cream), #8a7d55);
+ pointer-events: none;
+ }
+ .digital {
+ position: absolute; left: 50%; top: 33%; transform: translateX(-50%);
+ text-align: center; pointer-events: none;
+ }
+ .digital .big { color: var(--cream); font-size: 21px; font-weight: 700; font-variant-numeric: tabular-nums; }
+ .digital .big .u { color: var(--dim); font-size: 11px; }
+ .digital .lbl { color: var(--steel); font-size: 8.5px; letter-spacing: 0.22em; }
+
+ /* Subdials */
+ .subdial {
+ position: absolute; width: 74px; height: 74px; border-radius: 50%;
+ background: radial-gradient(circle at 40% 32%, #1b1914, #100e0a 75%);
+ border: 1px solid #322d24;
+ cursor: pointer;
+ }
+ .subdial .s-hand {
+ position: absolute; left: 50%; top: 9px; width: 2px; height: 28px;
+ margin-left: -1px;
+ background: var(--fail);
+ transform-origin: 50% 28px;
+ transition: transform .25s cubic-bezier(.3,1.4,.4,1);
+ pointer-events: none;
+ }
+ .subdial .s-cap {
+ position: absolute; left: 50%; top: 50%; width: 6px; height: 6px;
+ margin: -3px 0 0 -3px; border-radius: 50%; background: var(--cream);
+ pointer-events: none;
+ }
+ .subdial .s-lbl {
+ position: absolute; width: 100%; text-align: center; bottom: 13px;
+ color: var(--steel); font-size: 7.5px; letter-spacing: 0.14em;
+ pointer-events: none;
+ }
+ .subdial .s-val {
+ position: absolute; width: 100%; text-align: center; bottom: 4px;
+ color: var(--gold); font-size: 8.5px;
+ pointer-events: none;
+ }
+ .subdial.left { left: 42px; top: 113px; }
+ .subdial.right { right: 42px; top: 113px; }
+ .subdial.bottom{ left: 113px; bottom: 34px; }
+ .subdial:hover { border-color: var(--gold); }
+
+ /* Crown + pushers: momentary actions on the right case edge. */
+ .crown {
+ position: absolute; right: -12px; width: 16px; height: 26px;
+ border-radius: 4px;
+ background: linear-gradient(90deg, #2c2922, #191715);
+ border: 1px solid var(--key-edge);
+ cursor: pointer;
+ display: flex; align-items: center; justify-content: center;
+ color: var(--steel); font-size: 9px;
+ }
+ .crown:hover { border-color: var(--gold); color: var(--gold); }
+ .crown.c1 { top: 84px; transform: rotate(20deg); }
+ .crown.c2 { top: 140px; }
+ .crown.c3 { top: 196px; transform: rotate(-20deg); }
+ .crown-lbl {
+ position: absolute; right: -66px; width: 50px;
+ color: var(--dim); font-size: 8.5px; letter-spacing: 0.08em;
+ }
+
+ /* Bracelet: toggle links under the case. */
+ .bracelet { display: grid; grid-template-columns: repeat(4,1fr); gap: 5px; width: 100%; }
+ .link {
+ background: linear-gradient(180deg, #24211c, #171512);
+ border: 1px solid var(--key-edge);
+ border-radius: 5px;
+ padding: 6px 2px 5px;
+ display: flex; flex-direction: column; align-items: center; gap: 3px;
+ cursor: pointer;
+ }
+ .link:hover { border-color: var(--gold); }
+ .link .g { font-size: 14px; color: var(--steel); }
+ .link .n { font-size: 7.5px; letter-spacing: 0.06em; color: var(--dim); text-transform: uppercase; }
+ .link.on { border-color: var(--gold); background: linear-gradient(180deg, #2a2618, #1d1a12); }
+ .link.on .g { color: var(--gold); }
+ .link.on .n { color: var(--gold-bright); }
+ .link.verified { animation: dsVerify 0.9s ease-out; }
+</style>
+</head>
+<body class="ds-stage">
+ <div class="ds-caption">
+ <b>Direction 7 — Chronograph.</b> The panel is a watch. Drag around the
+ bezel to set brightness (the gold hand + digital window follow). Subdials:
+ keyboard backlight (drag or click), power profile and scene (click to step).
+ Crowns on the case are lock / suspend / wallpaper; the bracelet links are
+ the toggles.
+ </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 B_FLOOR = DS.SLIDERS.brightness.floor;
+const SWEEP = 270, START = -135; // main dial sweep
+
+function openWallpaper() {
+ subview.innerHTML = "";
+ subview.appendChild(DS.wallpaperSubview(() => subview.classList.remove("open")));
+ subview.classList.add("open");
+}
+
+const angleOf = (v, floor) => START + ((v - floor) / (100 - floor)) * SWEEP;
+
+// pointer position → dial value (angle from 12 o'clock, clamped to the sweep)
+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; // 0 at top, cw positive
+ a = Math.max(START, Math.min(START + SWEEP, a));
+ return Math.round(floor + ((a - START) / SWEEP) * (100 - floor));
+}
+
+function mainDial() {
+ const wrap = h("div", "face-wrap");
+ const bez = h("div", "bezel");
+
+ const ring = h("div", "ring");
+ const paintRing = (v) => {
+ const deg = ((v - B_FLOOR) / (100 - B_FLOOR)) * SWEEP;
+ ring.style.background =
+ `conic-gradient(from ${START}deg, var(--gold) 0 ${deg}deg, #21201b ${deg}deg ${SWEEP}deg, transparent ${SWEEP}deg 360deg)`;
+ ring.style.borderRadius = "50%";
+ };
+ paintRing(DS.state.brightness);
+ bez.appendChild(ring);
+ bez.appendChild(h("div", "dial"));
+
+ for (let i = 0; i <= 20; i++) {
+ const t = h("div", "tick" + (i % 5 === 0 ? " major" : ""));
+ t.style.transform = `rotate(${START + (i / 20) * SWEEP}deg)`;
+ bez.appendChild(t);
+ }
+
+ const hand = h("div", "hand");
+ const dig = h("div", "digital");
+ const paintHand = (v) => {
+ hand.style.transform = `rotate(${angleOf(v, B_FLOOR)}deg)`;
+ dig.innerHTML = `<div class="big">${v}<span class="u">%</span></div><div class="lbl">BRIGHTNESS</div>`;
+ };
+ paintHand(DS.state.brightness);
+ bez.appendChild(hand);
+ bez.appendChild(dig);
+ bez.appendChild(h("div", "cap"));
+
+ let live = DS.state.brightness;
+ bez.onpointerdown = (e) => {
+ if (e.target.closest(".subdial") || e.target.closest(".crown")) return;
+ bez.setPointerCapture(e.pointerId);
+ live = valueAt(e, bez, B_FLOOR); paintRing(live); paintHand(live);
+ bez.onpointermove = (m) => { live = valueAt(m, bez, B_FLOOR); paintRing(live); paintHand(live); };
+ bez.onpointerup = () => { bez.onpointermove = null; DS.setSlider("brightness", live, bez); };
+ };
+
+ // Subdial: keyboard backlight (drag same as main, smaller sweep display).
+ const kbd = h("div", "subdial left");
+ const kh = h("div", "s-hand");
+ const kFloor = DS.SLIDERS.kbd.floor;
+ const paintK = (v) => kh.style.transform = `rotate(${angleOf(v, kFloor)}deg)`;
+ paintK(DS.state.kbd);
+ kbd.appendChild(kh); kbd.appendChild(h("div", "s-cap"));
+ kbd.appendChild(h("div", "s-lbl", "KEYBOARD"));
+ const kv = h("div", "s-val", DS.state.kbd + "%");
+ kbd.appendChild(kv);
+ let kLive = DS.state.kbd;
+ kbd.onpointerdown = (e) => {
+ e.stopPropagation();
+ kbd.setPointerCapture(e.pointerId);
+ kLive = valueAt(e, kbd, kFloor); paintK(kLive); kv.textContent = kLive + "%";
+ kbd.onpointermove = (m) => { kLive = valueAt(m, kbd, kFloor); paintK(kLive); kv.textContent = kLive + "%"; };
+ kbd.onpointerup = () => { kbd.onpointermove = null; DS.setSlider("kbd", kLive, kbd); };
+ };
+
+ // Subdial: power profile — three detents, click steps.
+ const pw = h("div", "subdial right");
+ const pIdx = DS.PROFILES.findIndex((p) => p.id === DS.state.powerprofile);
+ const ph = h("div", "s-hand");
+ ph.style.transform = `rotate(${pIdx * 90 - 90}deg)`;
+ pw.appendChild(ph); pw.appendChild(h("div", "s-cap"));
+ pw.appendChild(h("div", "s-lbl", "POWER"));
+ pw.appendChild(h("div", "s-val", DS.PROFILES[pIdx].label.toUpperCase()));
+ pw.onclick = (e) => {
+ e.stopPropagation();
+ DS.setProfile(DS.PROFILES[(pIdx + 1) % DS.PROFILES.length].id, pw);
+ };
+
+ // Subdial: scene — off + four stations, click steps.
+ const sc = h("div", "subdial bottom");
+ const sIdx = SCENE_ORDER.indexOf(DS.state.scene); // -1 = off
+ const sh = h("div", "s-hand");
+ sh.style.transform = `rotate(${(sIdx + 1) * 72 - 144}deg)`;
+ sc.appendChild(sh); sc.appendChild(h("div", "s-cap"));
+ sc.appendChild(h("div", "s-lbl", "SCENE"));
+ sc.appendChild(h("div", "s-val", sIdx < 0 ? "OFF" : SCENE_ORDER[sIdx].toUpperCase()));
+ sc.onclick = (e) => {
+ e.stopPropagation();
+ const next = SCENE_ORDER[(sIdx + 1) % SCENE_ORDER.length];
+ DS.applyScene(next, sc);
+ };
+
+ bez.appendChild(kbd); bez.appendChild(pw); bez.appendChild(sc);
+ wrap.appendChild(bez);
+
+ // Crowns: momentary actions.
+ const mk = (cls, glyph, label, fn) => {
+ const c = h("div", "crown " + cls, glyph);
+ c.title = label;
+ c.onclick = (e) => { e.stopPropagation(); fn ? fn() : DS.momentary(label); };
+ return c;
+ };
+ wrap.appendChild(mk("c1", "\u{f033e}", "Lock"));
+ wrap.appendChild(mk("c2", "\u{f04b2}", "Suspend"));
+ wrap.appendChild(mk("c3", "\u{f021c}", "Wallpaper", openWallpaper));
+
+ return wrap;
+}
+
+function bracelet() {
+ const br = h("div", "bracelet");
+ 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", "link" + (DS.state[k] ? " on" : ""));
+ el.innerHTML = `<span class="g">${meta.glyph}</span><span class="n">${meta.name}</span>`;
+ el.onclick = () => DS.setToggle(k, el);
+ br.appendChild(el);
+ });
+ return br;
+}
+
+function render() {
+ body.innerHTML = "";
+ const cs = h("div", "case");
+ cs.appendChild(mainDial());
+ cs.appendChild(bracelet());
+ body.appendChild(cs);
+ document.getElementById("scene-sub").textContent = DS.state.scene ? "▶ " + DS.state.scene : "";
+}
+
+DS.onChange(render);
+render();
+</script>
+</body>
+</html>