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