diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-18 19:00:14 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-18 19:00:14 -0500 |
| commit | 576edce88a860e6f6284d1fe79e933eb1e0f3129 (patch) | |
| tree | ca80c94a9a28697432d27f677bd76a53b743707b /docs/prototypes/widgets.js | |
| parent | 334b553196f8c4c9d7136e1afa74847f2e13dc32 (diff) | |
| download | archsetup-576edce88a860e6f6284d1fe79e933eb1e0f3129.tar.gz archsetup-576edce88a860e6f6284d1fe79e933eb1e0f3129.zip | |
feat(gallery): bring the well-through-response ten to the extraction bar
outputWell, toast, annunciator, tapeCounter, freqScale, patchBay, dataMatrix, warningFlag, seg14, and responseGraph each get the contract comment. The six DOM builders take dupre- prefixes across their markup and CSS blocks. patchBay's live jack and the annunciator's master caution adopt the kit-wide dupre-hot and dupre-on modifiers, and the annunciator board takes dupre-warn and dupre-fault. freqScale reuses the shared scoped dupre-tick and dupre-mk (as the tuner and dualknob do) and compounds its unit label to dupre-fs-band, since dupre-band is the EQ's. The four SVG-stage builders style through the shared .rsvg stage and inline attributes, so they get the SVG-built contract comment and no prefixes.
The annunciator documented a 0..2 state per cell but painted an undefined class on an out-of-range initial state or set(). It now clamps at build and at set. Two probe checks cover the batch: one fails red without that clamp, one drives each renamed DOM builder through its handle to catch a class-name mismatch the exception scan can't see.
Extraction audit: 104/111 contract comments.
Diffstat (limited to 'docs/prototypes/widgets.js')
| -rw-r--r-- | docs/prototypes/widgets.js | 262 |
1 files changed, 183 insertions, 79 deletions
diff --git a/docs/prototypes/widgets.js b/docs/prototypes/widgets.js index bd5c79f..57ad33e 100644 --- a/docs/prototypes/widgets.js +++ b/docs/prototypes/widgets.js @@ -4346,15 +4346,28 @@ DUPRE.engravedLabel = function (host, opts = {}) { /* 22 output well — streaming step log, lamp per step; click streams the next demo step; push([lampCls, name, evidence]) appends programmatically */ +/* 22 output well — a scrolling log of status steps; click streams the next demo + step, oldest rows scroll off once the well is full. + + Contract (everything a consumer needs; no page globals touched): + opts: seed (initial rows, default two); steps (the click-cycle demo pool); + keep (max visible rows before the oldest scrolls off, default 5). + Each row is [tone, label, detail]; tone is '' | 'gold' | 'red' and + lights the row's dupre-lamp accent. onChange(row|null, caption) fires + on every push and once at build with a hint. + handle: el, push(row) — appends a row and trims the well to keep. Click + advances through steps and pushes the next. + CSS lives in the "output well" block of DUPRE_CSS; the row lamp is the shared + .dupre-lamp primitive tinted by the tone accent. */ DUPRE.outputWell = function (host, opts = {}) { const onChange = opts.onChange || noop; const seed = opts.seed || [['', 'Link', 'wlp170s0 · @Hyatt'], ['gold', 'DNS', 'resolving…']]; const steps = opts.steps || [['gold', 'Probe', '8.8.8.8 …'], ['', 'Gateway', '10.0.0.1 ok'], ['', 'DNS', '1.1.1.1 ok'], ['red', 'Retry', 'timeout']]; const keep = opts.keep || 5; - const w = document.createElement('div'); w.className = 'owell'; host.appendChild(w); + const w = document.createElement('div'); w.className = 'dupre-owell'; host.appendChild(w); const add = s => { - const d = document.createElement('div'); d.className = 'ostep'; - d.innerHTML = `<span class="dupre-lamp${s[0] ? ' dupre-' + s[0] : ''}"></span><span><b>${s[1]}</b><span class="ev">${s[2]}</span></span>`; + const d = document.createElement('div'); d.className = 'dupre-ostep'; + d.innerHTML = `<span class="dupre-lamp${s[0] ? ' dupre-' + s[0] : ''}"></span><span><b>${s[1]}</b><span class="dupre-ev">${s[2]}</span></span>`; w.appendChild(d); while (w.children.length > keep) w.removeChild(w.firstChild); }; seed.forEach(add); @@ -4366,11 +4379,19 @@ DUPRE.outputWell = function (host, opts = {}) { }; /* 23 toast — one-line transient confirmation; click fires the next demo - message; fire(msg) shows any message with the fade-in */ + message; fire(msg) shows any message with the fade-in. + + Contract (everything a consumer needs; no page globals touched): + opts: msgs (the demo message pool, cycled by click); text (initial + message, default the last pool entry); onChange(n, 'fired n') fires + on each click and once at build with a hint. + handle: el, fire(msg) — shows any message with the opacity fade-in; the + page can drive it with its own text. Click cycles the demo pool. + CSS lives in the "toast" block of DUPRE_CSS; no other styles involved. */ DUPRE.toast = function (host, opts = {}) { const onChange = opts.onChange || noop; const msgs = opts.msgs || ['link up · 300 Mbps', 'bt paired — WH-1000XM4', 'profile switched', 'joined @Hyatt_WiFi — saved']; - const t = document.createElement('span'); t.className = 'toastw'; + const t = document.createElement('span'); t.className = 'dupre-toastw'; t.textContent = opts.text || msgs[msgs.length - 1]; host.appendChild(t); const fire = msg => { @@ -4663,28 +4684,42 @@ DUPRE.vfdMarquee = function (host, opts = {}) { }; /* N23 annunciator — named alarm grid with the raise → MSTR CAUTION → ACK → RESET - lifecycle; TEST proves the bulbs then restores the board */ + lifecycle; TEST proves the bulbs then restores the board. + + Contract (everything a consumer needs; no page globals touched): + opts: cells (array of [label, state] where state is 0 ok · 1 warn · + 2 fault; defaults to a six-cell demo). onChange fires on every + transition: (count, summary) after a refresh, (index, 'label → name') + after a cell change, (null, 'lamp test') during TEST. + handle: el, get() (per-cell state array 0/1/2), set(i, st) — st clamps to + the 0..2 lens range at build AND at set, so an out-of-range caller + can't paint an undefined class. A new warn/fault re-arms the + MSTR CAUTION flasher; ACK stops it, RESET clears the board. + CSS lives in the "annunciator" block of DUPRE_CSS; the ACK/TEST/RESET buttons + are the shared .dupre-key primitive; the board uses the kit-wide dupre-warn + modifier and MSTR CAUTION the kit-wide dupre-on. */ DUPRE.annunciator = function (host, opts = {}) { const onChange = opts.onChange || noop; - const CLS = ['acell', 'acell warn', 'acell fault']; + const CLS = ['dupre-acell', 'dupre-acell dupre-warn', 'dupre-acell dupre-fault']; const NAMES = ['ok', 'warn', 'fault']; + const clampSt = st => Math.max(0, Math.min(CLS.length - 1, st | 0)); const cells = opts.cells || [['SYNC', 0], ['LOW BATT', 1], ['LINK', 0], ['NO DNS', 2], ['VPN', 0], ['DISK', 0]]; - const wrap = document.createElement('div'); wrap.className = 'annwrap'; - const grid = document.createElement('div'); grid.className = 'annun'; wrap.appendChild(grid); - const bar = document.createElement('div'); bar.className = 'annbar'; - const mc = document.createElement('span'); mc.className = 'mc'; mc.textContent = 'MSTR CAUTION'; bar.appendChild(mc); + const wrap = document.createElement('div'); wrap.className = 'dupre-annwrap'; + const grid = document.createElement('div'); grid.className = 'dupre-annun'; wrap.appendChild(grid); + const bar = document.createElement('div'); bar.className = 'dupre-annbar'; + const mc = document.createElement('span'); mc.className = 'dupre-mc'; mc.textContent = 'MSTR CAUTION'; bar.appendChild(mc); ['ACK', 'TEST', 'RESET'].forEach(a => { const b = document.createElement('button'); b.className = 'dupre-key'; b.dataset.k = a; b.textContent = a; bar.appendChild(b); }); wrap.appendChild(bar); host.appendChild(wrap); const els = cells.map(([label, st]) => { - const c = document.createElement('span'); c.className = CLS[st]; c.textContent = label; grid.appendChild(c); return c; + const c = document.createElement('span'); c.className = CLS[clampSt(st)]; c.textContent = label; grid.appendChild(c); return c; }); let acked = false; - const active = () => grid.querySelectorAll('.warn,.fault').length; + const active = () => grid.querySelectorAll('.dupre-warn,.dupre-fault').length; const refresh = () => { const n = active(); - mc.classList.toggle('on', n > 0); mc.classList.toggle('fl', n > 0 && !acked); + mc.classList.toggle('dupre-on', n > 0); mc.classList.toggle('dupre-fl', n > 0 && !acked); onChange(n, n === 0 ? 'clear' : n + ' active · ' + (acked ? 'ACK' : 'UNACK')); }; els.forEach((c, i) => c.addEventListener('click', () => { @@ -4695,18 +4730,18 @@ DUPRE.annunciator = function (host, opts = {}) { bar.querySelectorAll('.dupre-key').forEach(k => k.addEventListener('click', () => { const a = k.dataset.k; if (a === 'ACK') { if (active() > 0) acked = true; refresh(); } - else if (a === 'RESET') { els.forEach(c => c.className = 'acell'); acked = false; refresh(); } + else if (a === 'RESET') { els.forEach(c => c.className = 'dupre-acell'); acked = false; refresh(); } else if (a === 'TEST') { const prev = els.map(c => c.className); - els.forEach(c => c.className = 'acell fault'); - mc.classList.add('on', 'fl'); onChange(null, 'lamp test'); + els.forEach(c => c.className = 'dupre-acell dupre-fault'); + mc.classList.add('dupre-on', 'dupre-fl'); onChange(null, 'lamp test'); setTimeout(() => { els.forEach((c, i) => c.className = prev[i]); refresh(); }, 1100); } })); refresh(); return { el: wrap, get: () => els.map(c => Math.max(0, CLS.indexOf(c.className.trim()))), - set: (i, st) => { els[i].className = CLS[st]; if (st > 0) acked = false; refresh(); } + set: (i, st) => { els[i].className = CLS[clampSt(st)]; if (clampSt(st) > 0) acked = false; refresh(); } }; }; @@ -4749,14 +4784,22 @@ DUPRE.jewels = function (host, opts = {}) { return { el: wrap, get: () => states.slice(), set }; }; -/* N25 tape counter — odometer wheels; set(total) rolls each wheel to its digit */ +/* N25 tape counter — odometer wheels; set(total) rolls each wheel to its digit. + + Contract (everything a consumer needs; no page globals touched): + opts: wheels (digit count, default 6); redFrom (index from which the + high-order wheels wear the red band, default 4); value (initial, + default 471300); onChange(value, zero-padded string) fires on set. + handle: el, get() (current total), set(total) — rounds fractional input + and wraps modulo 10^wheels, then rolls each wheel to its digit. + CSS lives in the "tape counter" block of DUPRE_CSS; no other styles involved. */ DUPRE.tapeCounter = function (host, opts = {}) { const onChange = opts.onChange || noop; const wheels = opts.wheels || 6, redFrom = opts.redFrom !== undefined ? opts.redFrom : 4; - const ct = document.createElement('span'); ct.className = 'counter'; host.appendChild(ct); + const ct = document.createElement('span'); ct.className = 'dupre-counter'; host.appendChild(ct); for (let idx = 0; idx < wheels; idx++) { - const w = document.createElement('span'); w.className = 'cwheel' + (idx >= redFrom ? ' redw' : ''); - const col = document.createElement('span'); col.className = 'col'; + const w = document.createElement('span'); w.className = 'dupre-cwheel' + (idx >= redFrom ? ' dupre-redw' : ''); + const col = document.createElement('span'); col.className = 'dupre-col'; for (let n = -1; n <= 10; n++) { const sp = document.createElement('span'); sp.textContent = ((n + 10) % 10); col.appendChild(sp); } w.appendChild(col); ct.appendChild(w); } @@ -4765,7 +4808,7 @@ DUPRE.tapeCounter = function (host, opts = {}) { const set = x => { v = ((Math.round(x) % max) + max) % max; const s = String(v).padStart(wheels, '0'); - [...ct.children].forEach((w, i) => { w.querySelector('.col').style.top = (-(+s[i] + 1) * 34) + 'px'; }); + [...ct.children].forEach((w, i) => { w.querySelector('.dupre-col').style.top = (-(+s[i] + 1) * 34) + 'px'; }); onChange(v, s); }; set(opts.value !== undefined ? opts.value : 471300); @@ -4799,7 +4842,18 @@ DUPRE.analogClock = function (host, opts = {}) { }; /* N27 frequency-dial scale — printed log axis, marks crowd low and spread high; - drag the pointer to tune */ + drag the pointer to tune. + + Contract (everything a consumer needs; no page globals touched): + opts: width (px, default 184); marks (labeled major marks, log-spaced, + default 0.5..20); unit (band label, default 'MHz'); value (initial + pointer percent 0..100, default 45); onChange(freq, 'nn unit') fires + on set and drag. + handle: el, get() (pointer percent 0..100), set(pct) — clamps to 0..100 + and maps to a frequency along the log axis. dragX drives it. + CSS lives in the "frequency-dial scale" block of DUPRE_CSS; the ticks and + marks reuse the shared scoped dupre-tick/dupre-mk names (like the tuner and + dualknob), and the unit label is dupre-fs-band (dupre-band is the EQ's). */ DUPRE.freqScale = function (host, opts = {}) { const onChange = opts.onChange || noop; const W = opts.width || 184; @@ -4807,16 +4861,16 @@ DUPRE.freqScale = function (host, opts = {}) { const unit = opts.unit || 'MHz'; const lo = Math.log10(marks[0]), hi = Math.log10(marks[marks.length - 1]); const px = m => 6 + ((Math.log10(m) - lo) / (hi - lo)) * (W - 12); - const fs = document.createElement('span'); fs.className = 'freqscale'; - const band = document.createElement('span'); band.className = 'band'; band.textContent = unit; fs.appendChild(band); + const fs = document.createElement('span'); fs.className = 'dupre-freqscale'; + const band = document.createElement('span'); band.className = 'dupre-fs-band'; band.textContent = unit; fs.appendChild(band); marks.forEach(m => { - const tk = document.createElement('span'); tk.className = 'tick'; tk.style.left = px(m) + 'px'; tk.style.height = '11px'; fs.appendChild(tk); - const mk = document.createElement('span'); mk.className = 'mk'; mk.style.left = px(m) + 'px'; mk.textContent = m; fs.appendChild(mk); + const tk = document.createElement('span'); tk.className = 'dupre-tick'; tk.style.left = px(m) + 'px'; tk.style.height = '11px'; fs.appendChild(tk); + const mk = document.createElement('span'); mk.className = 'dupre-mk'; mk.style.left = px(m) + 'px'; mk.textContent = m; fs.appendChild(mk); }); for (let m = marks[0]; m <= marks[marks.length - 1]; m += (m < 2 ? 0.25 : m < 10 ? 1 : 2)) { - const tk = document.createElement('span'); tk.className = 'tick'; tk.style.left = px(m) + 'px'; tk.style.height = '6px'; tk.style.opacity = '.5'; fs.appendChild(tk); + const tk = document.createElement('span'); tk.className = 'dupre-tick'; tk.style.left = px(m) + 'px'; tk.style.height = '6px'; tk.style.opacity = '.5'; fs.appendChild(tk); } - const ptr = document.createElement('span'); ptr.className = 'fptr'; fs.appendChild(ptr); + const ptr = document.createElement('span'); ptr.className = 'dupre-fptr'; fs.appendChild(ptr); host.appendChild(fs); let pct; const set = p => { @@ -4831,15 +4885,25 @@ DUPRE.freqScale = function (host, opts = {}) { }; /* N28 patch bay — jack grid with SVG cables; click one jack then another to - patch or unpatch the pair; redraws itself on window resize */ + patch or unpatch the pair; redraws itself on window resize. + + Contract (everything a consumer needs; no page globals touched): + opts: rows (default 2), cols (default 4); conns (initial cables as + 'a-b' jack-index strings, default ['0-5', '2-7']); onChange(conns + array, summary) fires on every patch/unpatch and once at build. + handle: el, get() (conns copy), set(conns) — replaces the patch set and + redraws the cables. Click one jack then another to patch or + unpatch; the SVG cables re-solve on window resize. + CSS lives in the "patch-bay" block of DUPRE_CSS; a live jack wears the + kit-wide dupre-hot modifier. */ DUPRE.patchBay = function (host, opts = {}) { const onChange = opts.onChange || noop; const rows = opts.rows || 2, cols = opts.cols || 4; - const patch = document.createElement('div'); patch.className = 'patch'; + const patch = document.createElement('div'); patch.className = 'dupre-patch'; const jacks = []; for (let r = 0; r < rows; r++) { - const rowEl = document.createElement('div'); rowEl.className = 'row'; - for (let c = 0; c < cols; c++) { const j = document.createElement('span'); j.className = 'jack'; j.dataset.id = (r * cols + c); rowEl.appendChild(j); jacks.push(j); } + const rowEl = document.createElement('div'); rowEl.className = 'dupre-row'; + for (let c = 0; c < cols; c++) { const j = document.createElement('span'); j.className = 'dupre-jack'; j.dataset.id = (r * cols + c); rowEl.appendChild(j); jacks.push(j); } patch.appendChild(rowEl); } const svg = document.createElementNS(SVGNS, 'svg'); patch.appendChild(svg); @@ -4849,7 +4913,7 @@ DUPRE.patchBay = function (host, opts = {}) { const draw = () => { while (svg.firstChild) svg.removeChild(svg.firstChild); const pr = patch.getBoundingClientRect(); - jacks.forEach(j => j.classList.remove('hot')); + jacks.forEach(j => j.classList.remove('dupre-hot')); conns.forEach(pair => { const [a, b] = pair.split('-').map(Number); const ra = jacks[a].getBoundingClientRect(), rb = jacks[b].getBoundingClientRect(); @@ -4857,17 +4921,17 @@ DUPRE.patchBay = function (host, opts = {}) { const x2 = rb.left - pr.left + rb.width / 2, y2 = rb.top - pr.top + rb.height / 2; const p = document.createElementNS(SVGNS, 'path'); const my = Math.max(y1, y2) + 16; p.setAttribute('d', `M${x1} ${y1} C${x1} ${my} ${x2} ${my} ${x2} ${y2}`); svg.appendChild(p); - jacks[a].classList.add('hot'); jacks[b].classList.add('hot'); + jacks[a].classList.add('dupre-hot'); jacks[b].classList.add('dupre-hot'); }); onChange(conns.slice(), conns.length ? conns.map(c => { const [a, b] = c.split('-'); return a + '↔' + b; }).join(' ') : 'no cables'); }; jacks.forEach(j => { j.addEventListener('click', () => { const id = +j.dataset.id; - if (pending == null) { pending = id; j.classList.add('sel'); return; } - if (pending === id) { pending = null; j.classList.remove('sel'); return; } + if (pending == null) { pending = id; j.classList.add('dupre-sel'); return; } + if (pending === id) { pending = null; j.classList.remove('dupre-sel'); return; } const k = key(pending, id); const i = conns.indexOf(k); if (i >= 0) conns.splice(i, 1); else conns.push(k); - jacks[pending].classList.remove('sel'); pending = null; draw(); + jacks[pending].classList.remove('dupre-sel'); pending = null; draw(); }); }); draw(); window.addEventListener('resize', draw); @@ -4875,7 +4939,20 @@ DUPRE.patchBay = function (host, opts = {}) { }; /* R10 data matrix readout — a page of labeled amber fields; click cycles pages; - a page marked live re-renders on the builder's own 1s clock (reduced-motion gated) */ + a page marked live re-renders on the builder's own 1s clock (reduced-motion gated). + + Contract (everything a consumer needs; no page globals touched): + opts: pages (array of [name, rowsFn, live?] tuples, default SYS/NET/TIME; + rowsFn() returns the three text rows, and a truthy third element + marks a page that re-renders on the 1s clock); page (initial index, + default 0); onChange(index, 'page NAME') fires on set. + handle: el, get() (page index), set(i) — wraps modulo the page count. + Click cycles pages; a live page re-renders on the builder's own + 1s clock (reduced-motion gated). + SVG-built: styling is inline attributes plus the shared .rsvg stage block of + DUPRE_CSS; the glow filter registers in the shared defs plate, but the + screen-family background gradient lives in this instrument's local defs (it + reads --scr-* vars from its own subtree). */ DUPRE.dataMatrix = function (host, opts = {}) { const onChange = opts.onChange || noop; const pages = opts.pages || [ @@ -4927,7 +5004,16 @@ DUPRE.dataMatrix = function (host, opts = {}) { }; /* R11 warning flag window — the striped mechanical flag slides into the window - when the condition trips; click trips and clears it */ + when the condition trips; click trips and clears it. + + Contract (everything a consumer needs; no page globals touched): + opts: label (window legend, default 'VIB'); on (initial tripped state, + default false); onChange(on, 'FLAG'|'clear') fires on set. + handle: el, get() (tripped bool), set(v) — slides the striped flag into + the window when truthy, retracts it when not. Click toggles. + SVG-built: styling is inline attributes plus the shared .rsvg stage block of + DUPRE_CSS; the barber-stripe pattern registers in the shared defs plate, the + window clip in this instrument's local defs. */ DUPRE.warningFlag = function (host, opts = {}) { const onChange = opts.onChange || noop; const s = stageSvg(host, 'rsvg press', 120, 64); @@ -4956,7 +5042,16 @@ DUPRE.warningFlag = function (host, opts = {}) { }; /* R25 fourteen-segment display — the starburst alphanumeric that spells words; - click cycles the word */ + click cycles the word. + + Contract (everything a consumer needs; no page globals touched): + opts: words (the four-letter word pool cycled by click, default + ZOOM/ECHO/TAPE/…; letters outside the segment MAP render blank); + index (initial, default 0); onChange(index, word) fires on set. + handle: el, get() (word index), set(i) — wraps modulo the pool and lights + the fourteen-segment cells to spell the word. Click cycles. + SVG-built: styling is inline attributes plus the shared .rsvg stage block of + DUPRE_CSS; the segments are filled inline (lit vs --sevoff), no shared defs. */ DUPRE.seg14 = function (host, opts = {}) { const onChange = opts.onChange || noop; const words = opts.words || ['ZOOM', 'ECHO', 'TAPE', 'MOOD', 'HALL', 'COMP']; @@ -5014,7 +5109,16 @@ DUPRE.seg14 = function (host, opts = {}) { }; /* R26 response graph — log-frequency axes with a draggable amber peak; 2D drag - places the peak in both axes at once */ + places the peak in both axes at once. + + Contract (everything a consumer needs; no page globals touched): + opts: fc (initial center frequency Hz, default 1200); gain (initial dB, + default 6.5); onChange([fc, gain], 'f · ±g dB') fires on set and drag. + handle: el, get() ([fc, gain]), set(f, g) — fc clamps 32..16000 Hz, gain + clamps ±12 dB; draws the bell curve and moves the peak handle. A + 2D drag on the plot places the peak in both axes at once. + SVG-built: styling is inline attributes plus the shared .rsvg stage block of + DUPRE_CSS; the amber glow filter registers in the shared defs plate. */ DUPRE.responseGraph = function (host, opts = {}) { const onChange = opts.onChange || noop; const s = stageSvg(host, 'rsvg', 190, 110); @@ -5620,13 +5724,13 @@ const DUPRE_CSS = ` .dupre-wave svg{width:100%;height:100%;display:block} /* toast */ -.toastw{font-size:11px;color:var(--cream);background:var(--slate);border-radius:7px;padding:5px 10px;cursor:pointer} +.dupre-toastw{font-size:11px;color:var(--cream);background:var(--slate);border-radius:7px;padding:5px 10px;cursor:pointer} /* output well (log step) */ -.owell{width:200px;background:var(--well);border:1px solid var(--wash);border-radius:8px;padding:7px 9px;font-size:11px;cursor:pointer} -.ostep{display:flex;gap:7px;align-items:flex-start;padding:2px 0} -.ostep .dupre-lamp{margin-top:3px;width:7px;height:7px} -.ostep b{color:var(--cream);font-weight:700}.ostep .ev{color:var(--steel);display:block;font-size:10.5px} +.dupre-owell{width:200px;background:var(--well);border:1px solid var(--wash);border-radius:8px;padding:7px 9px;font-size:11px;cursor:pointer} +.dupre-ostep{display:flex;gap:7px;align-items:flex-start;padding:2px 0} +.dupre-ostep .dupre-lamp{margin-top:3px;width:7px;height:7px} +.dupre-ostep b{color:var(--cream);font-weight:700}.dupre-ostep .dupre-ev{color:var(--steel);display:block;font-size:10.5px} /* rotary selector */ .dupre-rotsel{position:relative;width:118px;height:74px} @@ -5916,19 +6020,19 @@ const DUPRE_CSS = ` background-image:radial-gradient(rgba(0,0,0,.6) 40%,transparent 41%);background-size:3px 3px} /* annunciator */ -.annwrap{display:flex;flex-direction:column;gap:6px} -.annbar{display:flex;gap:5px;align-items:center} -.mc{font-size:7px;letter-spacing:.08em;padding:4px 8px;border-radius:3px;background:#141210;color:#5c574c; +.dupre-annwrap{display:flex;flex-direction:column;gap:6px} +.dupre-annbar{display:flex;gap:5px;align-items:center} +.dupre-mc{font-size:7px;letter-spacing:.08em;padding:4px 8px;border-radius:3px;background:#141210;color:#5c574c; border:1px solid #26221c;text-align:center} -.mc.on{background:linear-gradient(180deg,#d98a6f,var(--fail));color:var(--cream);font-weight:700; +.dupre-mc.dupre-on{background:linear-gradient(180deg,#d98a6f,var(--fail));color:var(--cream);font-weight:700; box-shadow:0 0 8px rgba(203,107,77,.5)} -.mc.on.fl{animation:pulse var(--pulse-rate) ease-in-out infinite} -.annbar .dupre-key{padding:3px 7px;font-size:7.5px;border-radius:4px} -.annun{display:grid;grid-template-columns:repeat(3,1fr);gap:3px} -.acell{font-size:8.5px;letter-spacing:.08em;text-align:center;color:var(--dim);padding:6px 4px;border-radius:3px;cursor:pointer; +.dupre-mc.dupre-on.dupre-fl{animation:pulse var(--pulse-rate) ease-in-out infinite} +.dupre-annbar .dupre-key{padding:3px 7px;font-size:7.5px;border-radius:4px} +.dupre-annun{display:grid;grid-template-columns:repeat(3,1fr);gap:3px} +.dupre-acell{font-size:8.5px;letter-spacing:.08em;text-align:center;color:var(--dim);padding:6px 4px;border-radius:3px;cursor:pointer; background:#141210;border:1px solid #262320;line-height:1.2} -.acell.warn{color:var(--panel);background:linear-gradient(180deg,var(--amber-warn),var(--gold));font-weight:700;box-shadow:0 0 8px rgba(var(--glow-lo),.4)} -.acell.fault{color:var(--cream);background:linear-gradient(180deg,#d98a6f,var(--fail));font-weight:700;box-shadow:0 0 8px rgba(203,107,77,.4);animation:pulse var(--pulse-rate) ease-in-out infinite} +.dupre-acell.dupre-warn{color:var(--panel);background:linear-gradient(180deg,var(--amber-warn),var(--gold));font-weight:700;box-shadow:0 0 8px rgba(var(--glow-lo),.4)} +.dupre-acell.dupre-fault{color:var(--cream);background:linear-gradient(180deg,#d98a6f,var(--fail));font-weight:700;box-shadow:0 0 8px rgba(203,107,77,.4);animation:pulse var(--pulse-rate) ease-in-out infinite} /* jewel */ .dupre-jewel{width:24px;height:24px;border-radius:50%;position:relative;cursor:pointer; @@ -5940,17 +6044,17 @@ const DUPRE_CSS = ` .dupre-jewel.dupre-dim{background:radial-gradient(circle at 36% 30%,rgba(120,120,120,.3),#241f1b 55%,#0e0c0a);box-shadow:inset 0 -2px 3px rgba(0,0,0,.5)} /* tape counter */ -.counter{display:inline-flex;gap:2px;padding:4px;background:#0c0b0a;border:1px solid #2c261d;border-radius:4px; +.dupre-counter{display:inline-flex;gap:2px;padding:4px;background:#0c0b0a;border:1px solid #2c261d;border-radius:4px; box-shadow:inset 0 1px 3px rgba(0,0,0,.6)} -.cwheel{width:18px;height:34px;border-radius:2px;position:relative;overflow:hidden; +.dupre-cwheel{width:18px;height:34px;border-radius:2px;position:relative;overflow:hidden; background:linear-gradient(180deg,#efe9d6,#c7c0ac);box-shadow:inset 0 0 3px rgba(0,0,0,.3)} -.cwheel .col{position:absolute;left:0;right:0;text-align:center;color:#1a1613;font-size:18px;font-weight:700;line-height:34px; +.dupre-cwheel .dupre-col{position:absolute;left:0;right:0;text-align:center;color:#1a1613;font-size:18px;font-weight:700;line-height:34px; transition:top .35s cubic-bezier(.4,1.4,.5,1)} -.cwheel .col span{display:block;height:34px} -.cwheel::before,.cwheel::after{content:"";position:absolute;left:0;right:0;height:9px;z-index:2;pointer-events:none} -.cwheel::before{top:0;background:linear-gradient(180deg,rgba(0,0,0,.4),transparent)} -.cwheel::after{bottom:0;background:linear-gradient(0deg,rgba(0,0,0,.4),transparent)} -.counter .redw{background:linear-gradient(180deg,#e7b46a,#cf9440)} +.dupre-cwheel .dupre-col span{display:block;height:34px} +.dupre-cwheel::before,.dupre-cwheel::after{content:"";position:absolute;left:0;right:0;height:9px;z-index:2;pointer-events:none} +.dupre-cwheel::before{top:0;background:linear-gradient(180deg,rgba(0,0,0,.4),transparent)} +.dupre-cwheel::after{bottom:0;background:linear-gradient(0deg,rgba(0,0,0,.4),transparent)} +.dupre-counter .dupre-redw{background:linear-gradient(180deg,#e7b46a,#cf9440)} /* analog clock */ .dupre-clock{width:78px;height:78px;border-radius:50%;position:relative; @@ -5965,25 +6069,25 @@ const DUPRE_CSS = ` .dupre-clock .dupre-pin{position:absolute;left:50%;top:50%;width:6px;height:6px;margin:-3px;border-radius:50%;background:var(--gold)} /* frequency-dial scale */ -.freqscale{width:184px;height:44px;position:relative;border-radius:5px;overflow:hidden;cursor:pointer;touch-action:none; +.dupre-freqscale{width:184px;height:44px;position:relative;border-radius:5px;overflow:hidden;cursor:pointer;touch-action:none; background:linear-gradient(180deg,#191510,#0b0908);border:1px solid #2a251c; box-shadow:inset 0 0 16px rgba(var(--glow-lo),.1)} -.freqscale .tick{position:absolute;top:5px;width:1px;background:var(--steel);transform:translateX(-50%)} -.freqscale .mk{position:absolute;bottom:6px;transform:translateX(-50%);color:var(--steel);font-size:9px} -.freqscale .band{position:absolute;bottom:2px;left:6px;color:var(--gold);font-size:8px;letter-spacing:.18em} -.freqscale .fptr{position:absolute;top:3px;bottom:14px;width:2px;margin-left:-1px;border-radius:1px; +.dupre-freqscale .dupre-tick{position:absolute;top:5px;width:1px;background:var(--steel);transform:translateX(-50%)} +.dupre-freqscale .dupre-mk{position:absolute;bottom:6px;transform:translateX(-50%);color:var(--steel);font-size:9px} +.dupre-freqscale .dupre-fs-band{position:absolute;bottom:2px;left:6px;color:var(--gold);font-size:8px;letter-spacing:.18em} +.dupre-freqscale .dupre-fptr{position:absolute;top:3px;bottom:14px;width:2px;margin-left:-1px;border-radius:1px; background:var(--fail);box-shadow:0 0 7px rgba(203,107,77,.85)} /* patch-bay */ -.patch{padding:7px 9px;background:#141210;border:1px solid #2c261d;border-radius:5px;position:relative} -.patch .row{display:flex;gap:9px} -.patch .row+.row{margin-top:9px} -.patch .jack{width:13px;height:13px;border-radius:50%;cursor:pointer;background:radial-gradient(circle at 40% 35%,#3a352c,#0a0908 70%); +.dupre-patch{padding:7px 9px;background:#141210;border:1px solid #2c261d;border-radius:5px;position:relative} +.dupre-patch .dupre-row{display:flex;gap:9px} +.dupre-patch .dupre-row+.dupre-row{margin-top:9px} +.dupre-patch .dupre-jack{width:13px;height:13px;border-radius:50%;cursor:pointer;background:radial-gradient(circle at 40% 35%,#3a352c,#0a0908 70%); border:1px solid #4a443a;box-shadow:inset 0 1px 2px rgba(0,0,0,.8)} -.patch .jack.hot{background:radial-gradient(circle at 40% 35%,var(--amber-edge),#1a1408 70%)} -.patch .jack.sel{border-color:var(--gold-hi);box-shadow:0 0 6px 1px rgba(var(--glow-hi),.7)} -.patch svg{position:absolute;inset:0;pointer-events:none;width:100%;height:100%} -.patch svg path{fill:none;stroke:var(--gold);stroke-width:2.4;opacity:.85;stroke-linecap:round} +.dupre-patch .dupre-jack.dupre-hot{background:radial-gradient(circle at 40% 35%,var(--amber-edge),#1a1408 70%)} +.dupre-patch .dupre-jack.dupre-sel{border-color:var(--gold-hi);box-shadow:0 0 6px 1px rgba(var(--glow-hi),.7)} +.dupre-patch svg{position:absolute;inset:0;pointer-events:none;width:100%;height:100%} +.dupre-patch svg path{fill:none;stroke:var(--gold);stroke-width:2.4;opacity:.85;stroke-linecap:round} /* ===== reference-batch (R) instruments — SVG-first, after period hardware ===== */ .rsvg{display:block} |
