diff options
Diffstat (limited to 'docs/prototypes/2026-07-02-desktop-settings-panel-prototype-15.html')
| -rw-r--r-- | docs/prototypes/2026-07-02-desktop-settings-panel-prototype-15.html | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-15.html b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-15.html new file mode 100644 index 0000000..7bdc336 --- /dev/null +++ b/docs/prototypes/2026-07-02-desktop-settings-panel-prototype-15.html @@ -0,0 +1,299 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>Desktop Settings — Prototype 15 (power slide, full-width tuner)</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; + } + .row { display: flex; justify-content: center; align-items: center; gap: 10px; flex-wrap: wrap; } + .cellcap { color: var(--steel); font-size: 8.5px; letter-spacing: 0.14em; text-align: center; margin-top: 2px; text-transform: uppercase; } + .vcell { display: flex; flex-direction: column; align-items: center; } + .khost { display: flex; justify-content: center; } + .tcell { position: relative; } + .bat-oly { position: absolute; top: -8px; right: -10px; } + .bat-oly svg { width: 54px; height: 69px; } + + /* top block: presets over the tuner, compact chicken-head upper right */ + .topgrid { display: flex; gap: 8px; align-items: stretch; } + .topleft { flex: 1; display: flex; flex-direction: column; gap: 8px; } + .topright { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; } + .tuner-well { display: flex; justify-content: center; } + + .presets { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 5px; } + .preset { + background-image: linear-gradient(180deg, var(--raise), #131110); + border: 1px solid #33302b; border-bottom-width: 3px; border-radius: 6px; + padding: 4px 1px; text-align: center; cursor: pointer; color: var(--silver); + overflow: hidden; + } + .preset:hover { border-color: var(--gold); color: var(--gold); } + .preset .num { font-size: 7.5px; color: var(--dim); display: block; letter-spacing: 0; } + .preset .nm { font-size: 8.5px; letter-spacing: 0; display: block; } + .preset.cur { + border-color: var(--gold); border-bottom-width: 1px; transform: translateY(2px); + background-image: linear-gradient(180deg, #2a2618, #1d1a12); color: var(--cream); + } + .preset.cur .num { color: var(--gold); } + + /* levels + caffeine block: drums left, big timer right */ + .midgrid { display: flex; gap: 8px; align-items: center; justify-content: center; } + + /* switch wall: six cells, airplane's guarded lever among them */ + .wall { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px 6px; justify-items: center; align-items: end; } + + .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 15 — P14 + power slide.</b> The chicken-head is out: the + tuner dial is restored to full width, and beneath it a three-position + slide switch of equal length carries power — a status lamp per position, + labeled PERFORMANCE / BALANCE / POWER SAVE. Everything else as P14. + </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 STOPS = ["OFF", "FOCUS", "PRES", "BATT", "NIGHT"]; +const ROCKERS = [ + ["autodim", "Auto-dim"], ["touchpad", "Touchpad"], ["mouse", "Mouse"], + ["nightlight", "Night light"], ["dnd", "DND"], +]; +let booting = true; +let caffMinutes = 0; // survives re-renders +let caffHold = false; // the always-on rocker +let timerHost = null; // the timerDial is PERSISTENT: it owns a live + // countdown interval, so re-renders reparent it + // instead of rebuilding (rebuilds leak ghost + // intervals and kill an in-progress wind drag) + +const sceneIdx = () => (DS.state.scene ? SCENES.indexOf(DS.state.scene) + 1 : 0); +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 sceneOff() { + DS.state.scene = null; + DS.toast("Scene: off", true); + render(); +} + +// caffeine is on when held-on OR wound; keep DS in sync with that truth. +// Debounced: the sync triggers a panel re-render, which must not happen +// mid-wind (it would yank the dial out from under the drag). +function syncCaffeine(el) { + const on = caffHold || caffMinutes > 0; + if (!booting && on !== DS.state.caffeine) DS.setToggle("caffeine", el); +} +const syncCaffeineSoon = debounce(() => syncCaffeine(timerHost), 450); + +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)); +} + +function render() { + booting = true; + body.innerHTML = ""; + const col = h("div", "ds-col"); + + // ---- top: presets over the tuner; compact chicken-head upper right ----- + const top = h("div", "plate"); + const pr = h("div", "presets"); + SCENES.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); + pr.appendChild(k); + }); + top.appendChild(pr); + + const tw = h("div", "tuner-well"); + tw.style.marginTop = "8px"; + DUPRE.slideRule(tw, { + values: STOPS, + value: STOPS[sceneIdx()], + skin: "warm", + width: 306, // restored to the plate's full width + fmt: (v) => v, + onChange: (v) => { + if (booting) return; + if (v === "OFF") sceneOff(); + else DS.applyScene(SCENES[STOPS.indexOf(v) - 1], tw); + }, + }); + top.appendChild(tw); + + const pw = h("div", "khost"); + pw.style.marginTop = "8px"; + const pIdx = DS.PROFILES.findIndex((p) => p.id === DS.state.powerprofile); + DUPRE.slideSelector(pw, { + items: ["PERFORMANCE", "BALANCE", "POWER SAVE"], + index: pIdx, + width: 306, // equal to the tuner's length + onChange: (i) => { if (!booting && i !== pIdx) DS.setProfile(DS.PROFILES[i].id, pw); }, + }); + top.appendChild(pw); + col.appendChild(top); + + // ---- middle: drum partners left, larger caffeine timer right ------------ + col.appendChild(DS.engrave("Levels · Caffeine")); + 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: 189, // tall drums; the timer centers against them + 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: caffMinutes, + onChange: (min) => { + caffMinutes = min; + syncCaffeineSoon(); + }, + }); + scaleSvg(timerHost, 178); + // the bat toggle takes the winding arrow's corner + const svg = timerHost.querySelector("svg"); + [...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(); + } + tcell.appendChild(timerHost); + const batHost = h("div", "bat-oly"); + DUPRE.batToggle(batHost, { + on: !caffHold, // up = TIMED, down = HOLD + onLabel: "TIMED", + offLabel: "HOLD", + onChange: (on) => { + if (booting) return; + caffHold = !on; + syncCaffeine(batHost); + }, + }); + tcell.appendChild(batHost); + mid.appendChild(tcell); + col.appendChild(mid); + + // ---- switch wall: five rockers + the guarded airplane lever ------------- + col.appendChild(DS.engrave("Switches")); + const wall = h("div", "wall"); + ROCKERS.forEach(([k, name]) => { + const cell = h("div", "vcell"); + const host = h("div", "khost"); + DUPRE.rocker(host, { + on: DS.state[k], + onChange: (on) => { if (!booting && on !== DS.state[k]) DS.setToggle(k, cell); }, + }); + cell.appendChild(host); + cell.appendChild(h("div", "cellcap", name)); + wall.appendChild(cell); + }); + if (DS.state.laptop) { + const cell = h("div", "vcell"); + const host = h("div", "khost"); + DUPRE.guardedToggle(host, { + on: DS.state.airplane, + onLabel: "AIRPLANE", + offLabel: "RADIO OK", + onChange: (on) => { if (!booting && on !== DS.state.airplane) DS.setToggle("airplane", cell); }, + }); + scaleSvg(host, 72); + cell.appendChild(host); + wall.appendChild(cell); + } + col.appendChild(wall); + + // ---- wallpaper: where it was ------------------------------------------- + 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> |
