diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-22 15:15:02 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-22 15:15:02 -0500 |
| commit | f1c661f50d6af2a115fc1430ae0478f2acef2dec (patch) | |
| tree | b894ea2e14327794bb75671b962f37185cff22ea /docs/prototypes/2026-07-02-desktop-settings-panel-prototype-31.html | |
| parent | f594068bed75302aba50793ccf5e6ce9cf53c7f8 (diff) | |
| download | archsetup-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-31.html')
| -rw-r--r-- | docs/prototypes/2026-07-02-desktop-settings-panel-prototype-31.html | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-31.html b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-31.html new file mode 100644 index 0000000..9d9a0c1 --- /dev/null +++ b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-31.html @@ -0,0 +1,319 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>Desktop Settings — Prototype 31 (encircled groups)</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: stretch; justify-content: center; gap: 12px; } + .fcol { display: flex; flex-direction: column; gap: 12px; } + .fbox { + position: relative; + border: 1px solid #35302a; + border-radius: 10px; + padding: 14px 12px 10px; + display: flex; align-items: center; justify-content: center; gap: 10px; + } + .fbox .flbl { + position: absolute; top: -7px; left: 12px; + background: var(--raise); + padding: 0 7px; + color: var(--steel); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; + } + .fbox .dupre-key.ptr { width: 78px; height: 24px; font-size: 8px; letter-spacing: .08em; padding: 0; } + .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 31 — encircled groups.</b> TPAD and MOUSE become console + keys (lighting green), horizontal above the dial; the bench reorganizes + into three bordered instrument groups with their labels set into the + border: BRIGHTNESS · POINTERS · IDLE. Dial unchanged, drums grown to + proportion. + </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 — three encircled instrument groups ------------- + const lv = h("div", "plate"); + const bench = h("div", "bench"); + + const fbox = (label) => { + const b = h("div", "fbox"); + b.appendChild(h("span", "flbl", label)); + return b; + }; + + // BRIGHTNESS — the drum pair + const bright = fbox("Brightness"); + 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, bright); + if (k !== DS.state.kbd) DS.setSlider("kbd", k, bright); + }, 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: 204, + onChange: (vals) => { if (!booting) commitDrums(vals.slice()); }, + }); + bright.appendChild(dhost); + bench.appendChild(bright); + + const rcol = h("div", "fcol"); + + // POINTERS — console keys, horizontal + const ptr = fbox("Pointers"); + [["touchpad", "TPAD"], ["mouse", "MOUSE"]].forEach(([k, name]) => { + const b = h("button", "dupre-key ptr", name); + if (DS.state[k]) b.classList.add("dupre-green"); + b.onclick = () => { b.blur(); DS.setToggle(k, ptr); }; + ptr.appendChild(b); + }); + rcol.appendChild(ptr); + + // IDLE — the tripper dial (size unchanged) + const idle = fbox("Idle"); + 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); + idle.appendChild(railHost); + rcol.appendChild(idle); + + bench.appendChild(rcol); + 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> |
