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-6.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-6.html')
| -rw-r--r-- | docs/prototypes/2026-07-02-desktop-settings-panel-prototype-6.html | 343 |
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> |
