diff options
Diffstat (limited to 'docs/prototypes/2026-07-02-desktop-settings-panel-prototype-24.html')
| -rw-r--r-- | docs/prototypes/2026-07-02-desktop-settings-panel-prototype-24.html | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-24.html b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-24.html new file mode 100644 index 0000000..c473ce0 --- /dev/null +++ b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-24.html @@ -0,0 +1,304 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>Desktop Settings — Prototype 24 (tape wheel, jewel verdicts)</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:#e2a038; --gold-hi:#ffbe54; + --amber-grad-top:#f2c76a; --amber-grad-mid:#d29638; --amber-grad-bot:#8f671f; + --amber-edge:#7d5c16; --amber-warn:#f0b552; + --glow-hi:255,190,84; --glow-lo:226,160,56; + --mono:"BerkeleyMono Nerd Font","Berkeley Mono",monospace; + --pulse-rate:1s; + } + .plate { + background: + repeating-linear-gradient(90deg, #ffffff05 0 1px, transparent 1px 3px), + linear-gradient(180deg, #1d1a16, #141210); + border: 1px solid #262320; + border-radius: 10px; + padding: 10px; + } + .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 24 — tape wheel + jewel verdicts.</b> The triangles are + gone: click the tape-counter wheel itself to cycle the live CPU letter + (P → B → S, wrapping). In the active column every switch row shows a + verdict as a small jewel pilot lamp — green seated, red for a socket + the program holds off. The wheel cell is bigger, cream paper and + cylinder shade like the kit tape counter. + </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 = ["P", "B", "S"]; // ordered with DS.PROFILES +const LETTER_OF = { performance: "P", balanced: "B", saver: "S" }; +const PWR_OF = { P: "performance", B: "balanced", S: "saver" }; +let booting = true; +let sleepMinutes = 0; +let timerHost = null; // persistent: the timer owns a live countdown + +// normalize scene programs to COMPLETE definitions: recall = the column's +// pin pattern becomes the truth (pin on, no pin off; one power pin each) +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)); +} + +// the dial is a SLEEP TIMER now: wind minutes; zero = suspend (mocked) +function tameTimer(host) { + const svg = host.querySelector("svg"); + const NS = "http://www.w3.org/2000/svg"; + const defs = document.createElementNS(NS, "defs"); + defs.innerHTML = `<radialGradient id="p21Steel" cx="38%" cy="32%" r="80%"> + <stop offset="0" stop-color="#c9c4b8"/><stop offset="1" stop-color="#6e685c"/> + </radialGradient>`; + svg.insertBefore(defs, svg.firstChild); + svg.querySelectorAll('[fill="url(#mtRed)"]').forEach((n) => n.setAttribute("fill", "url(#p21Steel)")); + [...svg.querySelectorAll("text")].forEach((t) => { + if (t.textContent === "TURN TO START") t.remove(); + if (t.textContent === "PUSH TO STOP") t.textContent = "SLEEP TIMER · PUSH STOPS"; + if (t.textContent === "MINUTES") t.textContent = "SLEEP IN"; + if (t.textContent === "OFF") t.setAttribute("fill", "var(--gold-hi)"); + }); + [...svg.querySelectorAll("line")].forEach((l) => { + if (l.getAttribute("stroke") === "#e0523a") l.setAttribute("stroke", "var(--gold)"); + }); + svg.querySelectorAll('[stroke="#5c150c"]').forEach((n) => n.setAttribute("stroke", "#3c382f")); + [...svg.querySelectorAll("path")].find((p) => p.getAttribute("d").startsWith("M 122 14"))?.remove(); + [...svg.querySelectorAll("polygon")].find((p) => p.getAttribute("points").startsWith("136,26"))?.remove(); +} + +// 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("Program routing")); + const mxHost = h("div", "khost"); + const PROG_LABEL = { Focus: "FOCUS", Presentation: "PRESENT", Battery: "BATTERY", Night: "NIGHT" }; + const LABEL_PROG = Object.fromEntries(Object.entries(PROG_LABEL).map(([k, v]) => [v, k])); + const PROGS = SCENES.map((n) => PROG_LABEL[n]); + const pins = []; + SWITCH_ROWS.forEach(([lbl, key]) => { + SCENES.forEach((n) => { if (DS.SCENES[n].sets[key]) pins.push(lbl + ">" + PROG_LABEL[n]); }); + }); + const letters = {}; + SCENES.forEach((n) => { letters[PWR_ROW + ">" + PROG_LABEL[n]] = LETTER_OF[DS.SCENES[n].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, stepper: true }], + programs: PROGS, + pins, + letters, + groups: [SWITCH_ROWS.length, 1], + active: DS.state.scene ? SCENES.indexOf(DS.state.scene) : -1, + width: 306, + x0: 104, + engraved: true, + onActivate: (j) => { + if (booting) return; + DS.applyScene(SCENES[j], mxHost); + }, + onPin: (rowLbl, progLbl, seated, isActive) => { + if (booting) return; + const scene = LABEL_PROG[progLbl]; + 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(progLbl + " program updated", true); + }, + onLetter: (rowLbl, progLbl, v, isActive) => { + if (booting) return; + const scene = LABEL_PROG[progLbl]; + 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(progLbl + " program updated", true); + }, + onStep: (rowLbl) => { + if (booting) return; + const idx = DS.PROFILES.findIndex((p) => p.id === DS.state.powerprofile); + const n = (idx + 1) % DS.PROFILES.length; // the wheel is the button: cycle, wrapping + const scene = DS.state.scene; + DS.setProfile(DS.PROFILES[n].id, mxHost); + if (scene) { + DS.SCENES[scene].sets.powerprofile = DS.PROFILES[n].id; // active-is-live + stayActive(scene); + } + }, + }); + mx.setStepper(PWR_ROW, LETTER_OF[DS.state.powerprofile]); + plate.appendChild(mxHost); + col.appendChild(plate); + + // ---- plate 2: levels (the drums are back) + sleep timer ----------------- + const lv = h("div", "plate"); + lv.appendChild(DS.engrave("Levels · Sleep")); + const mid = h("div", "midgrid"); + + const dcell = h("div", "vcell"); + 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, dcell); + if (k !== DS.state.kbd) DS.setSlider("kbd", k, dcell); + }, 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: 132, + onChange: (vals) => { if (!booting) commitDrums(vals.slice()); }, + }); + dcell.appendChild(dhost); + mid.appendChild(dcell); + + const tcell = h("div", "vcell tcell"); + if (!timerHost) { + timerHost = h("div", "khost"); + DUPRE.timerDial(timerHost, { + minutes: 0, + onChange: (min) => { + const was = sleepMinutes; + sleepMinutes = min; + if (booting) return; + if (was > 0.5 && min === 0) DS.toast("Sleep timer expired — SUSPEND (mock)", true); + }, + }); + scaleSvg(timerHost, 132); + tameTimer(timerHost); + } + tcell.appendChild(timerHost); + mid.appendChild(tcell); + lv.appendChild(mid); + col.appendChild(lv); + + // ---- plate 3: pointing (never program-routed) --------------------------- + const pt = h("div", "plate"); + pt.appendChild(DS.engrave("Pointing")); + const strip = h("div", "pointing"); + [["touchpad", "TPAD"], ["mouse", "MOUSE"]].forEach(([k, name]) => { + const host = h("div", "khost"); + DUPRE.rocker(host, { + on: DS.state[k], + onLabel: name, + offLabel: "", + quiet: true, + accent: "green", + onChange: (on) => { if (!booting && on !== DS.state[k]) DS.setToggle(k, host); }, + }); + strip.appendChild(host); + }); + pt.appendChild(strip); + col.appendChild(pt); + + // ---- 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> |
