aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes/widgets.js
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-18 10:50:18 -0500
committerCraig Jennings <c@cjennings.net>2026-07-18 10:50:18 -0500
commitefd2e551dd727153463d22bd91d5cbe5484e7c3f (patch)
tree919ae62ef4cb4982baf521649179c3fcb0037354 /docs/prototypes/widgets.js
parentb0db5e842d93564b1cb2e4b142ba0a0de1843fbd (diff)
downloadarchsetup-efd2e551dd727153463d22bd91d5cbe5484e7c3f.tar.gz
archsetup-efd2e551dd727153463d22bd91d5cbe5484e7c3f.zip
refactor(gallery): rename GW namespace to DUPRE, kit to The Dupre Kit
The gallery and its library are now The Dupre Kit, named for the dupre color theme. GW was too opaque to remember. The stylesheet id is now dupre-css, the card handle property is .dupre, and page prose says instrument instead of widget. File names stay put. Renaming them is a separate decision. Historical log entries in todo.org keep their GW references as a record.
Diffstat (limited to 'docs/prototypes/widgets.js')
-rw-r--r--docs/prototypes/widgets.js390
1 files changed, 195 insertions, 195 deletions
diff --git a/docs/prototypes/widgets.js b/docs/prototypes/widgets.js
index 8af2666..2766353 100644
--- a/docs/prototypes/widgets.js
+++ b/docs/prototypes/widgets.js
@@ -1,12 +1,12 @@
-/* widgets.js — retro-instrument widget library (GW namespace).
+/* widgets.js — The Dupre Kit: retro-instrument component library (DUPRE namespace).
Classic script: load after the token :root block (tokens.json → gen_tokens.py),
- before any GW.* call. Works from file:// — no modules, no build step.
+ before any DUPRE.* call. Works from file:// — no modules, no build step.
Contract: each gallery card in panel-widget-gallery.html is the visual +
behavioral spec its builder is judged against.
Spec: docs/specs/2026-07-12-component-generation-spec.org */
(function () {
'use strict';
-const GW = {};
+const DUPRE = {};
/* ================= shared engine ================= */
@@ -107,19 +107,19 @@ const SCREEN_FAMS = {
'--crt-face1': '#d0d2d0', '--crt-face2': '#a8aaa8', '--crt-glow': '#f7f7f7' },
};
-/* ================= widget builders =================
- Every builder: GW.name(host, opts) → handle. host is an empty element the
- widget renders into. opts.onChange(value, text) fires on every state change,
- including the initial paint; text is the widget's canonical readout string. */
+/* ================= instrument builders =================
+ Every builder: DUPRE.name(host, opts) → handle. host is an empty element the
+ instrument renders into. opts.onChange(value, text) fires on every state change,
+ including the initial paint; text is the instrument's canonical readout string. */
const noop = () => {};
/* ---- the accent family ----
- One named set of lit colours for the widgets whose colour IS their claim: a
+ One named set of lit colours for the instruments whose colour IS their claim: a
chip, a lamp, a badge, a status line. "On" is good in one panel, a warning in
the next and a fault in the one after, so the colour belongs to the consumer
rather than the builder.
- Deliberately NOT applied to widgets whose colour is the object rather than a
+ Deliberately NOT applied to instruments whose colour is the object rather than a
state — the nixie (the palette is explicit that neon is only ever orange), the
flip-disc's yellow, the dekatron's glow, a red needle — nor to the ones where
the colour is a standard rather than a preference: three greens on the landing
@@ -134,60 +134,60 @@ const ACCENTS = {
white: 'var(--cream)',
vfd: 'var(--vfd)',
};
-GW.accentStyles = varName => Object.fromEntries(
+DUPRE.accentStyles = varName => Object.fromEntries(
Object.entries(ACCENTS).map(([name, colour]) => [name, { dot: colour, vars: { [varName]: colour } }]));
/* ---- policy ----
- Every widget's colour is either the consumer's to pick or locked for a reason,
- and the reason is one of these six kinds. Declared per builder (GW.<name>.POLICY)
+ Every instrument's colour is either the consumer's to pick or locked for a reason,
+ and the reason is one of these six kinds. Declared per builder (DUPRE.<name>.POLICY)
so it's a checked property rather than something the next person recalls — the
colour pass kept turning up cards where "is this one a standard?" was answered
from memory and answered wrong. The two FREE kinds get chips; the four LOCKED
ones must not, and the probe enforces the split.
- Policy is a property of the WIDGET, not the colour: the same vfd cyan is a free
+ Policy is a property of the INSTRUMENT, not the colour: the same vfd cyan is a free
accent on a chip and a locked emissive identity on the marquee. A few cards are
mixed (a free display with a coded red-line) and declare per element in time.
Each builder's POLICY is a record { kind, why, authentic }:
kind — one of the six below.
- why — why THIS widget is bound that way, in its own terms.
+ why — why THIS instrument is bound that way, in its own terms.
authentic — what may change and still be true to the reference (the range
that actually existed), or 'nothing' when the colour is fixed. */
-GW.POLICIES = {
+DUPRE.POLICIES = {
accent: { free: true, gist: 'A lit state whose meaning varies by panel; the consumer picks from the accent family.' },
screen: { free: true, gist: 'A display whose phosphor was made in several real colours; the consumer picks from the screen family.' },
coded: { free: false, gist: 'Colour is meaning fixed by an external standard; a recolour misleads a trained operator (landing-gear three-greens, breaker trip).' },
emissive: { free: false, gist: 'Colour is what the physical source emits and no other was made; unrecognisable otherwise (nixie neon, dekatron glow).' },
- relational: { free: false, gist: 'Colour means what it does only by contrast with another on the same widget; the set is a scale or legend (VU zones, crossed needles).' },
+ relational: { free: false, gist: 'Colour means what it does only by contrast with another on the same instrument; the set is a scale or legend (VU zones, crossed needles).' },
material: { free: false, gist: 'Colour of a static physical part, not a state — nothing to parameterise as a signal (needle, brass bezel, knife blade).' },
};
/* 01 slide toggle — on/off pill. State colors ride CSS vars (--sw-*).
opts.onStyle / offStyle / offText / thumb pick a named style per axis from
- GW.slideToggle.STYLES; defaults match the stylesheet fallbacks. The handle's
+ DUPRE.slideToggle.STYLES; defaults match the stylesheet fallbacks. The handle's
setStyle(axis, name) restyles a live instance (the gallery chips use it). */
-GW.slideToggle = function (host, opts = {}) {
+DUPRE.slideToggle = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const sw = document.createElement('span'); sw.className = 'switch'; host.appendChild(sw);
const setStyle = (axis, name) => {
- const o = (GW.slideToggle.STYLES[axis] || {})[name];
+ const o = (DUPRE.slideToggle.STYLES[axis] || {})[name];
if (!o) return;
for (const [k, v] of Object.entries(o.vars)) sw.style.setProperty(k, v);
};
/* opts.preset names an intent; per-axis opts still win over it, so a caller can
take 'armed' and swap only the thumb. setPreset re-applies all four axes at
once and returns the axis map so a host UI can resync its own controls. */
- const preset = GW.slideToggle.PRESETS[opts.preset] || GW.slideToggle.PRESETS.panel;
+ const preset = DUPRE.slideToggle.PRESETS[opts.preset] || DUPRE.slideToggle.PRESETS.panel;
/* AXIS_ORDER is load-bearing, not cosmetic: onText overrides the ink that the
`on` style sets, so applying the axes in any other order silently drops it. */
const setPreset = name => {
- const p = GW.slideToggle.PRESETS[name];
+ const p = DUPRE.slideToggle.PRESETS[name];
if (!p) return null;
- for (const axis of GW.slideToggle.AXIS_ORDER) if (p[axis]) setStyle(axis, p[axis]);
+ for (const axis of DUPRE.slideToggle.AXIS_ORDER) if (p[axis]) setStyle(axis, p[axis]);
return p;
};
const pick = { on: opts.onStyle, onText: opts.onText, off: opts.offStyle,
offText: opts.offText, thumb: opts.thumb };
- for (const axis of GW.slideToggle.AXIS_ORDER) setStyle(axis, pick[axis] || preset[axis]);
+ for (const axis of DUPRE.slideToggle.AXIS_ORDER) setStyle(axis, pick[axis] || preset[axis]);
let on;
const set = v => { on = !!v; sw.classList.toggle('on', on); onChange(on, on ? 'ON' : 'OFF'); };
sw.addEventListener('click', () => set(!on));
@@ -198,8 +198,8 @@ GW.slideToggle = function (host, opts = {}) {
actually reaches for; the STYLES axes below are how one is built. The `on`
tone does the semantic work and the thumb material backs it: a touchscreen
pill wears plastic, a run or armed switch wears metal. */
-GW.slideToggle.AXIS_ORDER = ['on', 'onText', 'off', 'offText', 'thumb'];
-GW.slideToggle.PRESETS = {
+DUPRE.slideToggle.AXIS_ORDER = ['on', 'onText', 'off', 'offText', 'thumb'];
+DUPRE.slideToggle.PRESETS = {
panel: { on: 'amber', onText: 'panel', off: 'dark', offText: 'white', thumb: 'light' },
run: { on: 'green', onText: 'panel', off: 'dark', offText: 'dim', thumb: 'chrome' },
armed: { on: 'red', onText: 'cream', off: 'dark', offText: 'white', thumb: 'chrome' },
@@ -210,7 +210,7 @@ GW.slideToggle.PRESETS = {
dark: { on: 'dark', onText: 'green', off: 'dark', offText: 'red', thumb: 'chrome' },
};
/* named styles per axis: dot = swatch color for pickers, vars = --sw-* overrides */
-GW.slideToggle.STYLES = {
+DUPRE.slideToggle.STYLES = {
on: {
amber: { dot: 'var(--gold-hi)', vars: { '--sw-on-bg': 'linear-gradient(180deg,var(--amber-grad-top),var(--gold))', '--sw-on-brd': 'var(--gold-hi)', '--sw-on-ink': 'var(--panel)' } },
green: { dot: '#8fb944', vars: { '--sw-on-bg': 'linear-gradient(180deg,#a9c95f,var(--pass))', '--sw-on-brd': '#a9c95f', '--sw-on-ink': 'var(--panel)' } },
@@ -259,9 +259,9 @@ GW.slideToggle.STYLES = {
run green, 'red' = terracotta. Reading order runs safe -> live -> muted, and
LIVE takes green because that is what --pass means everywhere else in the kit
(the palette names it "run lamps, gear greens, monitor bars, LIVE lamps"). */
-GW.consoleKeys = function (host, opts = {}) {
+DUPRE.consoleKeys = function (host, opts = {}) {
const onChange = opts.onChange || noop;
- const keys = opts.keys || GW.consoleKeys.DEFAULT_KEYS;
+ const keys = opts.keys || DUPRE.consoleKeys.DEFAULT_KEYS;
const wrap = document.createElement('span'); host.appendChild(wrap);
const btns = keys.map(k => {
const b = document.createElement('button'); b.className = 'key'; b.textContent = k.label;
@@ -281,10 +281,10 @@ GW.consoleKeys = function (host, opts = {}) {
resting state for a console and it shows the green. A caller supplying its
own keys gets the first one unless it says otherwise. */
set(opts.active !== undefined ? opts.active
- : (keys === GW.consoleKeys.DEFAULT_KEYS ? 1 : 0));
+ : (keys === DUPRE.consoleKeys.DEFAULT_KEYS ? 1 : 0));
return { el: wrap, get: () => idx, set };
};
-GW.consoleKeys.DEFAULT_KEYS = [
+DUPRE.consoleKeys.DEFAULT_KEYS = [
{ label: 'SCAN' }, { label: 'LIVE', tone: 'green' }, { label: 'MUTED', tone: 'red' },
];
@@ -303,7 +303,7 @@ GW.consoleKeys.DEFAULT_KEYS = [
already --fail and --pass.
- Letters left, digits right (the stainless reference's arrangement).
- DEL added, CANCEL dropped. See the layout block below for both. */
-GW.abcKeypad = function (host, opts = {}) {
+DUPRE.abcKeypad = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const MAX = opts.max || 16;
const s = stageSvg(host, 'rsvg', 232, 226);
@@ -342,7 +342,7 @@ GW.abcKeypad = function (host, opts = {}) {
Gated on ACTIONS rather than on KEYS because they are different sets — CLR
is a real key on the plate that no keystroke maps to. */
const press = k => {
- if (!GW.abcKeypad.ACTIONS.has(k)) return;
+ if (!DUPRE.abcKeypad.ACTIONS.has(k)) return;
if (k === 'ENT') { onChange(buf, buf ? 'ENTER · ' + buf : 'empty'); return; }
if (k === 'DEL') { buf = buf.slice(0, -1); render(); onChange(buf, buf || 'empty'); return; }
if (k === 'CLR') { buf = ''; render(); onChange(buf, 'cleared'); return; }
@@ -408,14 +408,14 @@ GW.abcKeypad = function (host, opts = {}) {
/* Keyboard, per the README's keyboard contract. The listener is bound to the
pad's own focusable element, never to the document: a global binding would
type into this card from anywhere on a 110-card page and fight the gallery's
- own Escape handler. GW.slideRule is the precedent. */
+ own Escape handler. DUPRE.slideRule is the precedent. */
s.setAttribute('class', 'rsvg kp-pad');
s.setAttribute('tabindex', '0');
s.addEventListener('click', () => s.focus());
s.addEventListener('keydown', e => {
if (e.ctrlKey || e.metaKey || e.altKey) return; /* leave shortcuts alone */
const name = e.key === ' ' ? 'Space' : (e.key.length === 1 ? e.key.toUpperCase() : e.key);
- const k = GW.abcKeypad.KEYS[name];
+ const k = DUPRE.abcKeypad.KEYS[name];
if (!k) return; /* not on the plate: let it bubble */
/* Spend preventDefault only where there is a default worth killing — Space
scrolls the page, Backspace can navigate back. Tab and Escape are never
@@ -430,10 +430,10 @@ GW.abcKeypad = function (host, opts = {}) {
/* The plate's keys, declared as a table so every target reads the same intent.
Deliberately not a function over a DOM event: the Emacs port installs this
into a keymap and never sees a keydown, so a function would force it to
- re-derive what the widget accepts and the two bindings would drift apart.
+ re-derive what the instrument accepts and the two bindings would drift apart.
This is also press()'s allowlist — press appends whatever it is handed, so
without the table a stray 'F1' would land in the buffer as text. */
-GW.abcKeypad.KEYS = (() => {
+DUPRE.abcKeypad.KEYS = (() => {
const m = {};
for (const c of 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') m[c] = c;
m.Space = 'SPC'; m.Backspace = 'DEL'; m.Enter = 'ENT';
@@ -442,7 +442,7 @@ GW.abcKeypad.KEYS = (() => {
/* The plate's whole vocabulary — every argument press accepts, from any caller.
A superset of the KEYS values: CLR is on the plate but no keystroke reaches it
(Escape is the obvious candidate and belongs to the gallery's audit stepper). */
-GW.abcKeypad.ACTIONS = new Set([
+DUPRE.abcKeypad.ACTIONS = new Set([
...'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 'SPC', 'DEL', 'ENT', 'CLR',
]);
@@ -463,10 +463,10 @@ GW.abcKeypad.ACTIONS = new Set([
same column offset: find the letter, then pick the case.
The layout is a table rather than drawing, because Craig has already said the
keys will be revisited and a layout welded into the geometry never is. */
-GW.indexPlate = function (host, opts = {}) {
+DUPRE.indexPlate = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const MAX = opts.max || 22;
- const L = GW.indexPlate.LAYOUT;
+ const L = DUPRE.indexPlate.LAYOUT;
const COLS = Math.max(...L.map(r => r.length)), ROWS = L.length;
/* Width is driven by the plate, not guessed: the lever and CLR live in a gutter
to its right. Sized from the layout table so a wider plate can't slide them
@@ -495,7 +495,7 @@ GW.indexPlate = function (host, opts = {}) {
The ring area carries a halftone screen, which is what the real plate prints
its outer characters onto. */
if (!document.getElementById('ixHalf')) {
- const defs = s.ownerDocument.querySelector('#gw-defs') || svgEl(s, 'defs', {});
+ const defs = s.ownerDocument.querySelector('#dupre-defs') || svgEl(s, 'defs', {});
const pat = svgEl(defs, 'pattern', { id: 'ixHalf', width: 4, height: 4, patternUnits: 'userSpaceOnUse' });
svgEl(pat, 'rect', { width: 4, height: 4, fill: 'url(#ixPlate)' });
svgEl(pat, 'circle', { cx: 1, cy: 1, r: 1.05, fill: '#2b2318', 'fill-opacity': .55 });
@@ -507,11 +507,11 @@ GW.indexPlate = function (host, opts = {}) {
a dark one for the lowercase — the plate's photographic-negative trick */
const ZFILL = { caps: '#e9e0bb', lower: '#231d13' };
const zoneAt = (r, c) => {
- const z = GW.indexPlate.ZONES.find(z =>
+ const z = DUPRE.indexPlate.ZONES.find(z =>
r >= z.rows[0] && r <= z.rows[1] && c >= z.cols[0] && c <= z.cols[1]);
return z ? z.zone : 'ring';
};
- GW.indexPlate.ZONES.forEach(z => {
+ DUPRE.indexPlate.ZONES.forEach(z => {
svgEl(s, 'rect', {
x: PX + z.cols[0] * CW - 1, y: PY + z.rows[0] * CH - 1,
width: (z.cols[1] - z.cols[0] + 1) * CW + 2, height: (z.rows[1] - z.rows[0] + 1) * CH + 2,
@@ -527,7 +527,7 @@ GW.indexPlate = function (host, opts = {}) {
/* The stylus: a cone on a short shaft, hovering over the selected cell. The
Mignon's arm reaches back to a pivot, but a full arm drawn here crosses the
plate and hides the characters the operator is trying to read — the one thing
- this widget must not do. A tip is enough to say "the pointer is here". */
+ this instrument must not do. A tip is enough to say "the pointer is here". */
const arm = svgEl(s, 'g', {}); arm.setAttribute('class', 'ix-stylus');
svgEl(arm, 'line', { x1: 0, y1: -9, x2: 0, y2: -20, stroke: 'url(#ixSteel)', 'stroke-width': 2.6 });
svgEl(arm, 'circle', { cx: 0, cy: -21, r: 3.4, fill: 'url(#ixSteel)', stroke: '#5c626e', 'stroke-width': .6 });
@@ -601,7 +601,7 @@ GW.indexPlate = function (host, opts = {}) {
primary input on this card would skip it and every probe would stay green.
Gated on ACTIONS, so it selects nothing it has no cell for. */
const press = k => {
- if (!GW.indexPlate.ACTIONS.has(k)) return;
+ if (!DUPRE.indexPlate.ACTIONS.has(k)) return;
if (k === 'PRINT') return print();
if (k === 'CLR') return fresh();
select(k);
@@ -617,7 +617,7 @@ GW.indexPlate = function (host, opts = {}) {
s.addEventListener('click', () => s.focus());
s.addEventListener('keydown', e => {
if (e.ctrlKey || e.metaKey || e.altKey) return;
- const k = GW.indexPlate.KEYS[e.key];
+ const k = DUPRE.indexPlate.KEYS[e.key];
if (!k) return;
e.preventDefault(); /* Enter would submit a form; a cell key has no default worth keeping */
press(k);
@@ -639,7 +639,7 @@ GW.indexPlate = function (host, opts = {}) {
- J and j live out on the ring, not in the case blocks, which is why the
inversion below is positional and not "capitals are dark".
The two dashes differ: a long one top-right, a hyphen bottom-right. */
-GW.indexPlate.LAYOUT = [
+DUPRE.indexPlate.LAYOUT = [
['&','(',')',':','"','!', '?',"'",'ä','ö','ü','—'],
['§','P','F','U','G','Q', 'p','f','u','g','q',';'],
['J','V','I','N','A','B', 'v','i','n','a','b','j'],
@@ -654,7 +654,7 @@ GW.indexPlate.LAYOUT = [
the outer ring stays light-on-halftone — including the capital J, which is why
no case-based rule can describe this. Move a block, move its rectangle.
rows and cols are inclusive [start, end] indices into LAYOUT. */
-GW.indexPlate.ZONES = [
+DUPRE.indexPlate.ZONES = [
{ rows: [1, 5], cols: [1, 5], zone: 'caps' },
{ rows: [1, 5], cols: [6, 10], zone: 'lower' },
];
@@ -663,18 +663,18 @@ GW.indexPlate.ZONES = [
Every plate character maps to itself (no case folding — the plate has both, so
Shift does the work a shift key would), and Enter is the lever. Space is
deliberately absent: there's no space cell yet, so Space isn't ours to claim. */
-GW.indexPlate.KEYS = (() => {
+DUPRE.indexPlate.KEYS = (() => {
const m = {};
- for (const c of GW.indexPlate.LAYOUT.flat()) if (c) m[c] = c;
+ for (const c of DUPRE.indexPlate.LAYOUT.flat()) if (c) m[c] = c;
m.Enter = 'PRINT';
return m;
})();
/* Every argument press accepts. A superset of the KEYS values: CLR is a real
control that no keystroke reaches, the same shape as the keypad's. */
-GW.indexPlate.ACTIONS = new Set([...GW.indexPlate.LAYOUT.flat().filter(Boolean), 'PRINT', 'CLR']);
+DUPRE.indexPlate.ACTIONS = new Set([...DUPRE.indexPlate.LAYOUT.flat().filter(Boolean), 'PRINT', 'CLR']);
/* 03 horizontal fader — continuous 0-100 */
-GW.faderH = function (host, opts = {}) {
+DUPRE.faderH = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const f = document.createElement('div'); f.className = 'fader';
f.innerHTML = '<div class="slot"><div class="fill"></div></div><div class="cap"></div>';
@@ -692,7 +692,7 @@ GW.faderH = function (host, opts = {}) {
};
/* 04 vertical fader — one channel-strip fader; compose per channel */
-GW.faderV = function (host, opts = {}) {
+DUPRE.faderV = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const f = document.createElement('div'); f.className = 'vfader';
f.innerHTML = '<div class="slot"><div class="fill"></div></div><div class="cap"></div>';
@@ -710,7 +710,7 @@ GW.faderV = function (host, opts = {}) {
};
/* 05 rotary knob — drag up/down to turn, -150°..+150° sweep */
-GW.knob = function (host, opts = {}) {
+DUPRE.knob = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const min = opts.min !== undefined ? opts.min : 0, max = opts.max !== undefined ? opts.max : 100;
const k = document.createElement('span'); k.className = 'knob';
@@ -728,14 +728,14 @@ GW.knob = function (host, opts = {}) {
};
/* 06 segmented selector — pick one of a few. opts.accent picks the lit-segment
- color from GW.segmented.STYLES (amber / green / red); defaults match the
+ color from DUPRE.segmented.STYLES (amber / green / red); defaults match the
stylesheet fallbacks. setStyle(axis, name) restyles a live instance. */
-GW.segmented = function (host, opts = {}) {
+DUPRE.segmented = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const items = opts.items || ['TIMER', 'ALARM', 'POMO'];
const seg = document.createElement('div'); seg.className = 'seg'; host.appendChild(seg);
const setStyle = (axis, name) => {
- const o = (GW.segmented.STYLES[axis] || {})[name];
+ const o = (DUPRE.segmented.STYLES[axis] || {})[name];
if (!o) return;
for (const [k, v] of Object.entries(o.vars)) seg.style.setProperty(k, v);
};
@@ -748,7 +748,7 @@ GW.segmented = function (host, opts = {}) {
return { el: seg, get: () => idx, set, setStyle };
};
/* named styles per axis: dot = swatch color for pickers, vars = --seg-* overrides */
-GW.segmented.STYLES = {
+DUPRE.segmented.STYLES = {
accent: {
amber: { dot: 'var(--gold-hi)', vars: { '--seg-on-bg': 'linear-gradient(180deg,var(--amber-grad-top),var(--gold))', '--seg-on-ink': 'var(--panel)' } },
green: { dot: '#8fb944', vars: { '--seg-on-bg': 'linear-gradient(180deg,#a9c95f,var(--pass))', '--seg-on-ink': 'var(--panel)' } },
@@ -757,13 +757,13 @@ GW.segmented.STYLES = {
};
/* 07 chip toggle — inline binary inside a line of text */
-GW.chipToggle = function (host, opts = {}) {
+DUPRE.chipToggle = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const chip = document.createElement('span'); chip.className = 'chip';
chip.textContent = opts.label || 'discoverable on';
host.appendChild(chip);
const setStyle = (axis, name) => {
- const o = (GW.chipToggle.STYLES[axis] || {})[name];
+ const o = (DUPRE.chipToggle.STYLES[axis] || {})[name];
if (!o) return;
for (const [k, v] of Object.entries(o.vars)) chip.style.setProperty(k, v);
};
@@ -778,10 +778,10 @@ GW.chipToggle = function (host, opts = {}) {
let the chip say exactly one thing — but "on" is good in one panel, a warning in
the next, and a fault in the one after. The colour is the claim, so it belongs
to the consumer. */
-GW.chipToggle.STYLES = { accent: GW.accentStyles('--chip-on') };
+DUPRE.chipToggle.STYLES = { accent: DUPRE.accentStyles('--chip-on') };
/* 08 arm-to-fire — two-stage confirm for destructive actions */
-GW.armButton = function (host, opts = {}) {
+DUPRE.armButton = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const label = opts.label || 'forget', armLabel = opts.armLabel || label + '? again';
const a = document.createElement('button'); a.className = 'arm'; a.textContent = label;
@@ -796,7 +796,7 @@ GW.armButton = function (host, opts = {}) {
};
/* 09 lamp row — actionable list item: lamp + name + status, click to cycle */
-GW.lampRow = function (host, opts = {}) {
+DUPRE.lampRow = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const name = opts.name || 'WH-1000XM4';
const states = opts.states || [['gold', 'tap to connect'], ['busy', 'connecting…'], ['', 'connected']];
@@ -817,7 +817,7 @@ GW.lampRow = function (host, opts = {}) {
};
/* 24 rotary selector — pick one of five printed detents by position */
-GW.rotarySelector = function (host, opts = {}) {
+DUPRE.rotarySelector = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const values = opts.values || [4, 6, 8, 10, 12];
const fmt = opts.fmt || (v => 'size ' + v);
@@ -846,9 +846,9 @@ GW.rotarySelector = function (host, opts = {}) {
are the majors; the integer units between them get minor ticks and are
selectable too. Click a numeral, a mark, or between marks; ←/→ (↑/↓) step
one unit. set() takes a stop index; get() returns the value. opts.skin picks
- a face from GW.slideRule.STYLES (warm backlit / chrome / 80s black glass / marantz blue);
+ a face from DUPRE.slideRule.STYLES (warm backlit / chrome / 80s black glass / marantz blue);
setStyle(axis, name) restyles a live instance. */
-GW.slideRule = function (host, opts = {}) {
+DUPRE.slideRule = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const majors = opts.values || [4, 6, 8, 10, 12];
const fmt = opts.fmt || (v => 'pos ' + v);
@@ -865,7 +865,7 @@ GW.slideRule = function (host, opts = {}) {
const t = document.createElement('span'); t.className = 'tuner';
t.tabIndex = 0; t.setAttribute('role', 'slider'); t.setAttribute('aria-label', 'slide-rule value');
const setStyle = (axis, name) => {
- const o = (GW.slideRule.STYLES[axis] || {})[name];
+ const o = (DUPRE.slideRule.STYLES[axis] || {})[name];
if (!o) return;
for (const [k, v] of Object.entries(o.vars)) t.style.setProperty(k, v);
};
@@ -902,7 +902,7 @@ GW.slideRule = function (host, opts = {}) {
};
/* named faces: dot = swatch color for pickers, vars = --tn-* overrides.
warm restates the stylesheet fallbacks so switching back is exact. */
-GW.slideRule.STYLES = {
+DUPRE.slideRule.STYLES = {
skin: {
warm: { dot: 'var(--gold-hi)', vars: {
'--tn-bg': 'linear-gradient(180deg,#191510,#0b0908)', '--tn-brd': '#2a251c',
@@ -928,7 +928,7 @@ GW.slideRule.STYLES = {
};
/* N01 rocker power switch — hard on/off, lit legend */
-GW.rocker = function (host, opts = {}) {
+DUPRE.rocker = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const r = document.createElement('span'); r.className = 'rocker';
r.innerHTML = `<span class="half top">${opts.onLabel || 'ON'}</span><span class="half bot">${opts.offLabel || 'OFF'}</span>`;
@@ -942,7 +942,7 @@ GW.rocker = function (host, opts = {}) {
/* N02 transport cluster — rew/play/stop/rec, one lit; reels turn while playing.
opts.animate: reels obey play state (default: not prefers-reduced-motion) */
-GW.transport = function (host, opts = {}) {
+DUPRE.transport = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const animate = opts.animate !== undefined ? opts.animate : !matchMedia('(prefers-reduced-motion: reduce)').matches;
const MODES = { rew: '⏮', play: '▶', stop: '⏹', rec: '⏺' };
@@ -973,7 +973,7 @@ GW.transport = function (host, opts = {}) {
};
/* N03 radio preset bank — mechanically exclusive presets */
-GW.presetBank = function (host, opts = {}) {
+DUPRE.presetBank = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const items = opts.items || ['WIFI', 'ETH', 'CELL', 'OFF'];
const bank = document.createElement('div'); bank.className = 'radiobank'; host.appendChild(bank);
@@ -986,7 +986,7 @@ GW.presetBank = function (host, opts = {}) {
};
/* N04 concentric dual knob — two values on one spindle, outer ring + inner cap */
-GW.dualKnob = function (host, opts = {}) {
+DUPRE.dualKnob = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const dk = document.createElement('span'); dk.className = 'dualknob';
dk.innerHTML = `<span class="outer"><span class="tick" style="transform:rotate(-120deg)"></span><span class="tick" style="transform:rotate(120deg)"></span></span><span class="inner"><span class="ind"></span></span>`;
@@ -1007,7 +1007,7 @@ GW.dualKnob = function (host, opts = {}) {
};
/* N05 rotary encoder + LED ring — endless dial, lit arc tracks the level */
-GW.encoder = function (host, opts = {}) {
+DUPRE.encoder = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const enc = document.createElement('span'); enc.className = 'encoder'; host.appendChild(enc);
const R = 27, cx = 33, cy = 33;
@@ -1032,7 +1032,7 @@ GW.encoder = function (host, opts = {}) {
};
/* N06 keyed mode switch — guarded three-position mode, key-bit points at the live one */
-GW.keySwitch = function (host, opts = {}) {
+DUPRE.keySwitch = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const items = opts.items || ['OFF', 'ON', 'RUN'];
const ANG = [-70, 0, 70], POS = [[16, 64], [50, 12], [84, 64]];
@@ -1058,7 +1058,7 @@ GW.keySwitch = function (host, opts = {}) {
};
/* N07 center-detented crossfader — throw to either side of zero */
-GW.crossfader = function (host, opts = {}) {
+DUPRE.crossfader = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const xf = document.createElement('div'); xf.className = 'xfader';
xf.innerHTML = `<div class="slot"></div><div class="detent"></div><span class="end" style="left:0">${opts.aLabel || 'A'}</span><span class="end" style="right:0">${opts.bLabel || 'B'}</span><div class="cap"></div>`;
@@ -1076,7 +1076,7 @@ GW.crossfader = function (host, opts = {}) {
};
/* N08 thumbwheel — knurled edge-wheel with a windowed two-digit value */
-GW.thumbwheel = function (host, opts = {}) {
+DUPRE.thumbwheel = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const w = document.createElement('div'); w.className = 'thumbw';
w.innerHTML = '<span class="thumbwheel"></span><span class="win"></span>';
@@ -1094,7 +1094,7 @@ GW.thumbwheel = function (host, opts = {}) {
};
/* N09 DIP-switch bank — hard flags, up is on; readout is the binary word */
-GW.dipBank = function (host, opts = {}) {
+DUPRE.dipBank = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const bits = opts.bits || [true, false, true, true, false, false];
const bank = document.createElement('span'); bank.className = 'dip'; host.appendChild(bank);
@@ -1110,7 +1110,7 @@ GW.dipBank = function (host, opts = {}) {
};
/* N10 jog / shuttle wheel — scrub fine; position accumulates without limit */
-GW.jogWheel = function (host, opts = {}) {
+DUPRE.jogWheel = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const j = document.createElement('span'); j.className = 'jog';
j.innerHTML = `<span class="shuttle" style="--sh:40deg"></span><span class="inner"><span class="dimple"></span></span>`;
@@ -1127,7 +1127,7 @@ GW.jogWheel = function (host, opts = {}) {
};
/* ---- shared SVG defs: gradients/filters referenced by url(#id) are
- document-scoped, and several are used across widgets. Builders ensure the
+ document-scoped, and several are used across instruments. Builders ensure the
defs they use; the first caller creates it, later calls are no-ops. ---- */
let defsRoot = null;
const defsMade = new Set();
@@ -1161,7 +1161,7 @@ let uidN = 0;
function uid(prefix) { return prefix + '-' + (++uidN); }
/* R02 calibrated vernier dial — the disc turns under a fixed hairline */
-GW.vernierDial = function (host, opts = {}) {
+DUPRE.vernierDial = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg drag', 150, 150), cx = 75, cy = 75;
gradDef('vernFace', 'radialGradient', { cx: '50%', cy: '45%', r: '60%' }, [['0', '#f7f2da'], ['1', '#e4dfc2']]);
@@ -1191,7 +1191,7 @@ GW.vernierDial = function (host, opts = {}) {
};
/* R03 bat-handle toggle — chrome lever throws between lit legends */
-GW.batToggle = function (host, opts = {}) {
+DUPRE.batToggle = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 70, 90), cx = 35, cy = 44;
gradDef('nutG', 'linearGradient', { x1: 0, y1: 0, x2: 0, y2: 1 }, [['0', '#8a8578'], ['1', '#4e4a42']]);
@@ -1222,7 +1222,7 @@ GW.batToggle = function (host, opts = {}) {
};
/* R04 bakelite fluted knob — scallop skirt over a printed 0-10 arc */
-GW.flutedKnob = function (host, opts = {}) {
+DUPRE.flutedKnob = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg drag', 110, 110), cx = 55, cy = 54;
gradDef('bakeSk', 'radialGradient', { cx: '42%', cy: '36%', r: '80%' }, [['0', '#312d27'], ['1', '#050404']]);
@@ -1249,7 +1249,7 @@ GW.flutedKnob = function (host, opts = {}) {
};
/* R05 filter slider bank — a dense fader wall on a real faceplate. Period skins
- as constructor opts backed by GW.filterBank.STYLES, three independent axes:
+ as constructor opts backed by DUPRE.filterBank.STYLES, three independent axes:
panel (silver hi-fi aluminum / studio black), caps — the cap SHAPE
(chrome T / short ribbed / tall block fader), and capColor — the cap
FINISH (black white-index / color stripes / chrome / cream). opts.style
@@ -1257,13 +1257,13 @@ GW.flutedKnob = function (host, opts = {}) {
opts.panel / opts.caps / opts.capColor override an axis; setStyle(axis, name)
restyles live (values kept). After the Pioneer SG-9500, the Technics
SH-8065, and the Zaxcom Oasis block faders. */
-GW.filterBank = function (host, opts = {}) {
+DUPRE.filterBank = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const freqs = opts.freqs || [78, 113, 163, 235, 339, 487, 701, 1010, 1450, 2090, 3010, 4340];
const vals = (opts.values || [18, 30, 42, 25, 35, 55, 20, 48, 38, 26, 44, 32]).slice(0, freqs.length);
while (vals.length < freqs.length) vals.push(30);
const fmtHz = f => f < 1000 ? f : (f / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
- const ST = GW.filterBank.STYLES;
+ const ST = DUPRE.filterBank.STYLES;
const native = {
silver: { caps: 'chrome', capColor: 'chrome' },
studio: { caps: 'ribbed', capColor: 'red' },
@@ -1352,7 +1352,7 @@ GW.filterBank = function (host, opts = {}) {
/* named styles per axis: dot = swatch color for pickers. caps is the cap
shape; capColor is the cap finish (body + rib + index line), applicable to
any shape. */
-GW.filterBank.STYLES = {
+DUPRE.filterBank.STYLES = {
panel: {
silver: { dot: '#c9c9c5', plate: 'url(#fbPlateSilver)', edge: '#8e8e88', bevel: 'rgba(255,255,255,.4)', ink: '#33332f', dim: '#5c5c56', screw: '#9c9c96', track: '#121210', trackW: 2.0, trackHi: null },
studio: { dot: '#1c1a18', plate: 'url(#fbPlateStudio)', edge: '#000', bevel: 'rgba(255,255,255,.06)', ink: '#b9b4a6', dim: '#6f6a5e', screw: '#3c3832', track: '#050505', trackW: 2.2, trackHi: 'rgba(255,255,255,.05)' },
@@ -1374,7 +1374,7 @@ GW.filterBank.STYLES = {
};
/* R06 chicken-head selector — tapered lever aims at the position */
-GW.chickenHead = function (host, opts = {}) {
+DUPRE.chickenHead = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const items = opts.items || [['OFF', -60], ['LO', -20], ['MID', 20], ['HI', 60]];
const s = stageSvg(host, 'rsvg press', 110, 96), cx = 55, cy = 58;
@@ -1405,7 +1405,7 @@ GW.chickenHead = function (host, opts = {}) {
};
/* R12 chrome slot fader — engraved dB scale, chrome T-handle in a screwed plate */
-GW.slotFader = function (host, opts = {}) {
+DUPRE.slotFader = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg drag', 90, 150), cx = 45, yTop = 30, yBot = 120;
const yOf = db => yTop + (12 - db) / 36 * (yBot - yTop);
@@ -1442,7 +1442,7 @@ GW.slotFader = function (host, opts = {}) {
};
/* R14 spade-pointer tuning knob — engraved relief arc, knurl ring turns with it */
-GW.spadeKnob = function (host, opts = {}) {
+DUPRE.spadeKnob = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg drag', 140, 124), cx = 70, cy = 82;
const sweep = v => -80 + v / 10 * 160;
@@ -1481,7 +1481,7 @@ GW.spadeKnob = function (host, opts = {}) {
};
/* R15 multi-band dial — nested arcs, one needle; the bandspread dial selects the ring */
-GW.multiBandDial = function (host, opts = {}) {
+DUPRE.multiBandDial = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const ranges = opts.ranges || [[0.54, 1.6], [1.6, 5.1], [5.1, 15.5], [15.5, 30.5]];
const s = stageSvg(host, 'rsvg', 190, 110), cx = 62, cy = 62;
@@ -1532,7 +1532,7 @@ GW.multiBandDial = function (host, opts = {}) {
};
/* R16 entry keypad — worn keys feed the amber display; lamps watch state */
-GW.entryKeypad = function (host, opts = {}) {
+DUPRE.entryKeypad = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg', 150, 190);
gradDef('kpKey', 'linearGradient', { x1: 0, y1: 0, x2: 0, y2: 1 }, [['0', '#dbd8cf'], ['1', '#a29d92']]);
@@ -1570,7 +1570,7 @@ GW.entryKeypad = function (host, opts = {}) {
};
/* R18 thumb-slide attenuator pair — lit numeral strip, cream side tab */
-GW.thumbSlide = function (host, opts = {}) {
+DUPRE.thumbSlide = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const chans = (opts.channels || [{ name: 'BLEND', v: 74 }, { name: 'MIX', v: 92 }]).map(c => ({ name: c.name, v: c.v }));
const s = stageSvg(host, 'rsvg', 130, 150), y0 = 22, y1 = 122;
@@ -1616,7 +1616,7 @@ GW.thumbSlide = function (host, opts = {}) {
};
/* R19 waveform region editor — monochrome LCD, draggable S/E flags */
-GW.waveRegion = function (host, opts = {}) {
+DUPRE.waveRegion = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg', 190, 100), x0 = 10, x1 = 180, yMid = 48, N = 85;
svgEl(s, 'rect', { x: 1, y: 1, width: 188, height: 98, rx: 6, fill: 'var(--scr-bg1,#0b0c0b)', stroke: 'var(--scr-brd,#2a2c2a)', 'stroke-width': 2 });
@@ -1667,7 +1667,7 @@ GW.waveRegion = function (host, opts = {}) {
};
/* R20 drum roller selector — numbered paper drum in a window, center chip reads it */
-GW.drumRoller = function (host, opts = {}) {
+DUPRE.drumRoller = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const chans = (opts.channels || [{ name: 'HIGH', v: 6 }, { name: 'LOW', v: 8 }]).map(c => ({ name: c.name, v: c.v }));
const s = stageSvg(host, 'rsvg', 130, 140), cy = 76, step = 17;
@@ -1710,7 +1710,7 @@ GW.drumRoller = function (host, opts = {}) {
};
/* R21 LED program row — exclusive select, the LED above the key carries the state */
-GW.ledRow = function (host, opts = {}) {
+DUPRE.ledRow = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const items = opts.items || ['Sm Hall B', 'VocPlate', 'Lg Hall B', 'Chamber', 'ParcPlate', 'Sm Hall A', 'Room A', 'Const Plate'];
const s = stageSvg(host, 'rsvg', 190, 58);
@@ -1741,7 +1741,7 @@ GW.ledRow = function (host, opts = {}) {
};
/* R22 three-position slide — chrome pill between detents, honest LED pair */
-GW.pillSlide = function (host, opts = {}) {
+DUPRE.pillSlide = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const positions = opts.positions || ['A', 'AB', 'B'];
const s = stageSvg(host, 'rsvg press', 110, 64), detX = [34, 55, 76];
@@ -1776,7 +1776,7 @@ GW.pillSlide = function (host, opts = {}) {
};
/* R23 spun-aluminum knob — machined rings in a knurled grip, red index */
-GW.spunKnob = function (host, opts = {}) {
+DUPRE.spunKnob = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg drag', 110, 110), cx = 55, cy = 52;
gradDef('spunFace', 'radialGradient', { cx: '42%', cy: '34%', r: '85%' }, [['0', '#e8e6e0'], ['.7', '#b3b0a8'], ['1', '#8a877e']]);
@@ -1801,7 +1801,7 @@ GW.spunKnob = function (host, opts = {}) {
};
/* R24 stomp switch + jewel — press to engage, the jewel reports */
-GW.stompSwitch = function (host, opts = {}) {
+DUPRE.stompSwitch = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 110, 110), cx = 55;
gradDef('stompDome', 'radialGradient', { cx: '40%', cy: '30%', r: '80%' }, [['0', '#f4f2ec'], ['.55', '#b9b6ae'], ['1', '#6e6b63']]);
@@ -1835,7 +1835,7 @@ GW.stompSwitch = function (host, opts = {}) {
};
/* R27 winged gain selector — red T-bar over a dot ring, stepped detents */
-GW.wingSelector = function (host, opts = {}) {
+DUPRE.wingSelector = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const steps = opts.steps || [-80, -70, -60, -50, -40, -30, -20, -10, -5, 0, 5, 10];
const s = stageSvg(host, 'rsvg drag', 110, 110), cx = 55, cy = 52;
@@ -1868,7 +1868,7 @@ GW.wingSelector = function (host, opts = {}) {
};
/* R28 rotary disc switch — the whole disc turns between heavy positions */
-GW.discSwitch = function (host, opts = {}) {
+DUPRE.discSwitch = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const positions = opts.positions || [['OFF', -45], ['ON', 45], ['COMBINE', 135]];
const s = stageSvg(host, 'rsvg press', 110, 116), cx = 55, cy = 58;
@@ -1903,7 +1903,7 @@ GW.discSwitch = function (host, opts = {}) {
};
/* R29 guarded toggle — guard posts + red collar mark the critical throw */
-GW.guardedToggle = function (host, opts = {}) {
+DUPRE.guardedToggle = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 90, 100), cx = 45, cy = 52;
gradDef('discRed', 'radialGradient', { cx: '42%', cy: '34%', r: '85%' }, [['0', '#d98a6f'], ['.65', 'var(--fail)'], ['1', '#7a2a1a']]);
@@ -1942,7 +1942,7 @@ GW.guardedToggle = function (host, opts = {}) {
/* R32 mechanical timer dial — dial rotates under a fixed index; wind by drag, stop by the red knob.
Runs its own 1 Hz wind-down (a demo minute per second) unless reduced motion is set. */
-GW.timerDial = function (host, opts = {}) {
+DUPRE.timerDial = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg', 158, 132), cx = 62, cy = 58;
gradDef('mtFace', 'radialGradient', { cx: '50%', cy: '42%', r: '75%' }, [['0', '#282320'], ['1', '#14110e']]);
@@ -2003,7 +2003,7 @@ GW.timerDial = function (host, opts = {}) {
};
/* R33 four-way rocker — quadrant clicks step a tracked cursor; arrows flash on press */
-GW.rockerPad = function (host, opts = {}) {
+DUPRE.rockerPad = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 110, 110), cx = 55, cy = 55;
gradDef('rk4Pad', 'radialGradient', { cx: '42%', cy: '34%', r: '85%' }, [['0', '#33302b'], ['1', '#141210']]);
@@ -2042,7 +2042,7 @@ GW.rockerPad = function (host, opts = {}) {
};
/* R34 four-way toggle selector — ball lever throws to a diagonal; corner lamps show the state */
-GW.fourWayToggle = function (host, opts = {}) {
+DUPRE.fourWayToggle = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 130, 130), cx = 65, cy = 65;
gradDef('fw4Chrome', 'linearGradient', { x1: 0, y1: 0, x2: 1, y2: 1 }, [['0', '#e8e6e0'], ['.5', '#9a968c'], ['1', '#55524a']]);
@@ -2100,7 +2100,7 @@ GW.fourWayToggle = function (host, opts = {}) {
};
/* R37 pin routing matrix — click an intersection to seat/pull a pin; many-to-many */
-GW.pinMatrix = function (host, opts = {}) {
+DUPRE.pinMatrix = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const ROWS = opts.rows || ['OSC1', 'OSC2', 'LFO', 'NOIS', 'ENV'];
const COLS = opts.cols || ['VCF', 'VCA', 'PAN', 'DLY', 'OUT', 'MOD'];
@@ -2128,7 +2128,7 @@ GW.pinMatrix = function (host, opts = {}) {
};
/* R38 dead-man button — state exists only while the pointer holds it down */
-GW.deadMan = function (host, opts = {}) {
+DUPRE.deadMan = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg', 120, 110), cx = 60, cy = 58;
gradDef('dmBtn', 'radialGradient', { cx: '40%', cy: '32%', r: '85%' }, [['0', '#3a3631'], ['1', '#16130f']]);
@@ -2168,7 +2168,7 @@ GW.deadMan = function (host, opts = {}) {
};
/* R39 rotary telephone dial — click a hole; the wheel winds to the stop and returns */
-GW.telephoneDial = function (host, opts = {}) {
+DUPRE.telephoneDial = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 130, 130), cx = 65, cy = 63;
const DIGITS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
@@ -2220,7 +2220,7 @@ GW.telephoneDial = function (host, opts = {}) {
};
/* R40 circuit breaker panel — on/off by click, TRIP pops one to the amber mid-state, reset is two-step */
-GW.breakerPanel = function (host, opts = {}) {
+DUPRE.breakerPanel = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const NAMES = opts.names || ['MAIN', 'PUMP', 'LAMP', 'AUX'];
const s = stageSvg(host, 'rsvg press', 160, 110);
@@ -2260,7 +2260,7 @@ GW.breakerPanel = function (host, opts = {}) {
};
/* R41 DSKY — verb/noun command grammar with status lamps and a lamp-test verb */
-GW.dsky = function (host, opts = {}) {
+DUPRE.dsky = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const LAMPS = ['UPLINK', 'TEMP', 'GIMBAL', 'PROG', 'RESTART', 'OPR ERR'];
const el = document.createElement('div'); el.className = 'dsky'; host.appendChild(el);
@@ -2312,7 +2312,7 @@ GW.dsky = function (host, opts = {}) {
/* R42 cam-timer program drum — the program is a ring; the pointer self-advances through it.
Runs its own reduced-motion-gated 2 s step interval once started. */
-GW.camTimer = function (host, opts = {}) {
+DUPRE.camTimer = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const STEPS = opts.steps || ['OFF', 'FILL', 'WASH', 'WASH', 'WASH', 'RINSE', 'RINSE', 'DRAIN', 'SPIN', 'SPIN', 'FLUFF', 'COOL'];
const COLS = opts.colors || { OFF: '#3a3426', FILL: '#54677d', WASH: '#d29638', RINSE: '#46b89e', DRAIN: '#969385', SPIN: '#cb6b4d', FLUFF: '#c9b98a', COOL: '#7c99b0' };
@@ -2348,7 +2348,7 @@ GW.camTimer = function (host, opts = {}) {
};
/* R48 knife switch (side view) — the blade hinges at the left post and lands in the right jaw */
-GW.knifeSwitch = function (host, opts = {}) {
+DUPRE.knifeSwitch = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 130, 110);
gradDef('ksCu', 'linearGradient', { x1: 0, y1: 0, x2: 0, y2: 1 }, [['0', '#d09a5c'], ['1', '#8a5a2a']]);
@@ -2383,7 +2383,7 @@ GW.knifeSwitch = function (host, opts = {}) {
};
/* R49 decade box — four skirted knobs, one digit each; the value is their sum */
-GW.decadeBox = function (host, opts = {}) {
+DUPRE.decadeBox = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const MUL = [1000, 100, 10, 1], LBL = ['x1000', 'x100', 'x10', 'x1'];
const digs = (opts.digits || [3, 5, 0, 0]).slice();
@@ -2419,7 +2419,7 @@ GW.decadeBox = function (host, opts = {}) {
};
/* R50 two-hand safety — one palm button arms a 500ms window; the other completes the cycle */
-GW.twoHandSafety = function (host, opts = {}) {
+DUPRE.twoHandSafety = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 160, 100);
svgEl(s, 'rect', { x: 2, y: 2, width: 156, height: 96, rx: 8, fill: '#1c1916', stroke: '#060505', 'stroke-width': 1.5 });
@@ -2462,7 +2462,7 @@ GW.twoHandSafety = function (host, opts = {}) {
};
/* R51 voice-loop keyset — independent monitor states, exclusive talk, activity flicker */
-GW.voiceLoop = function (host, opts = {}) {
+DUPRE.voiceLoop = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const LOOPS = opts.loops || ['FD', 'GNC', 'ECOM', 'SURG', 'A/G', 'NET1', 'NET2', 'PAO'];
const el = document.createElement('div'); el.className = 'vloop'; host.appendChild(el);
@@ -2508,7 +2508,7 @@ GW.voiceLoop = function (host, opts = {}) {
belongs to the instrument (peak-hold, history buffers) lives in here. */
/* 10 needle gauge — drag up/down sweeps the needle over a 120° arc */
-GW.needleGauge = function (host, opts = {}) {
+DUPRE.needleGauge = function (host, opts = {}) {
const onChange = opts.onChange || noop;
let v = opts.value ?? 50;
const el = document.createElement('div'); el.className = 'gauge';
@@ -2529,7 +2529,7 @@ GW.needleGauge = function (host, opts = {}) {
};
/* 11 stereo VU — two LED bars with peak-hold; set(l, r) drives both */
-GW.vuPair = function (host, opts = {}) {
+DUPRE.vuPair = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const n = opts.bars || 16;
const el = document.createElement('div'); el.className = 'vu';
@@ -2558,7 +2558,7 @@ GW.vuPair = function (host, opts = {}) {
};
/* 12 mini 4-bar signal — compact activity meter; set(level) */
-GW.miniSig = function (host, opts = {}) {
+DUPRE.miniSig = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const el = document.createElement('span'); el.className = 'sig';
el.innerHTML = '<i></i><i></i><i></i><i></i>';
@@ -2574,7 +2574,7 @@ GW.miniSig = function (host, opts = {}) {
};
/* 13 signal ladder — stepped 0-4 strength; click cycles */
-GW.signalLadder = function (host, opts = {}) {
+DUPRE.signalLadder = function (host, opts = {}) {
const onChange = opts.onChange || noop;
let v = opts.value ?? 3;
const el = document.createElement('span'); el.className = 'ladder';
@@ -2592,7 +2592,7 @@ GW.signalLadder = function (host, opts = {}) {
};
/* 14 linear fuel bar — one 0-100 bar, warn tint under the threshold; drag to set */
-GW.fuelBar = function (host, opts = {}) {
+DUPRE.fuelBar = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const warnAt = opts.warnAt ?? 20;
let v = opts.value ?? 50;
@@ -2612,7 +2612,7 @@ GW.fuelBar = function (host, opts = {}) {
};
/* 15 radial ring — percentage donut; drag up/down to set */
-GW.radialRing = function (host, opts = {}) {
+DUPRE.radialRing = function (host, opts = {}) {
const onChange = opts.onChange || noop;
let v = opts.value ?? 68;
const el = document.createElement('span'); el.className = 'ring';
@@ -2631,7 +2631,7 @@ GW.radialRing = function (host, opts = {}) {
};
/* 16 sparkline — rolling history trace; push(v) appends a sample, fill(v) levels it */
-GW.sparkline = function (host, opts = {}) {
+DUPRE.sparkline = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const n = opts.samples || 40;
const hist = Array.from({ length: n }, () => opts.value ?? 0.5);
@@ -2650,7 +2650,7 @@ GW.sparkline = function (host, opts = {}) {
};
/* 17 waveform strip — sampled trace; set(samples, amp) with samples in -1..1 */
-GW.waveStrip = function (host, opts = {}) {
+DUPRE.waveStrip = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const el = document.createElement('span'); el.className = 'wave';
el.innerHTML = '<svg viewBox="0 0 170 38" preserveAspectRatio="none"><path fill="none" stroke="var(--gold)" stroke-width="1.2"/></svg>';
@@ -2671,7 +2671,7 @@ GW.waveStrip = function (host, opts = {}) {
};
/* N11 oscilloscope — sampled phosphor trace; set(samples, vpp) with samples in -1..1 */
-GW.scope = function (host, opts = {}) {
+DUPRE.scope = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const el = document.createElement('span'); el.className = 'scope';
el.innerHTML = '<span class="grat"></span><svg viewBox="0 0 176 78" preserveAspectRatio="none"><polyline/></svg>';
@@ -2688,7 +2688,7 @@ GW.scope = function (host, opts = {}) {
};
/* N12 spectrum / EQ bars — set(values) paints one column per band */
-GW.eqBars = function (host, opts = {}) {
+DUPRE.eqBars = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const bands = opts.bands || 11, cells = opts.cells || 9;
const el = document.createElement('span'); el.className = 'eq';
@@ -2717,7 +2717,7 @@ GW.eqBars = function (host, opts = {}) {
};
/* N13 crossed-needle meter — one drive value, FWD and RFL needles cross */
-GW.crossNeedle = function (host, opts = {}) {
+DUPRE.crossNeedle = function (host, opts = {}) {
const onChange = opts.onChange || noop;
let v = opts.value ?? 55;
const el = document.createElement('div'); el.className = 'crossm';
@@ -2738,7 +2738,7 @@ GW.crossNeedle = function (host, opts = {}) {
};
/* N14 thermometer column — mercury rises with the value */
-GW.thermometer = function (host, opts = {}) {
+DUPRE.thermometer = function (host, opts = {}) {
const onChange = opts.onChange || noop;
let v = opts.value ?? 58;
const el = document.createElement('div'); el.className = 'thermo';
@@ -2757,7 +2757,7 @@ GW.thermometer = function (host, opts = {}) {
};
/* N15 bourdon pressure gauge — needle over a printed arc with a redline */
-GW.bourdon = function (host, opts = {}) {
+DUPRE.bourdon = function (host, opts = {}) {
const onChange = opts.onChange || noop;
let v = opts.value ?? 45;
const el = document.createElement('div'); el.className = 'bourdon';
@@ -2778,7 +2778,7 @@ GW.bourdon = function (host, opts = {}) {
/* N16 strip-chart recorder — scrolling history, pen rides the newest value;
push(v) appends, set(samples, current) replaces the trace (values 0..1) */
-GW.stripChart = function (host, opts = {}) {
+DUPRE.stripChart = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const n = opts.samples || 60;
const hist = Array.from({ length: n }, () => opts.value ?? 0.5);
@@ -2802,7 +2802,7 @@ GW.stripChart = function (host, opts = {}) {
};
/* N17 correlation meter — needle rests at 0 and swings ±; drag left/right */
-GW.corrMeter = function (host, opts = {}) {
+DUPRE.corrMeter = function (host, opts = {}) {
const onChange = opts.onChange || noop;
let p = opts.value ?? 58;
const el = document.createElement('div'); el.className = 'corr';
@@ -2822,7 +2822,7 @@ GW.corrMeter = function (host, opts = {}) {
};
/* N18 battery-cell gauge — charge as discrete cells, the low end warns */
-GW.battCells = function (host, opts = {}) {
+DUPRE.battCells = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const cells = opts.cells || 8, warnAt = opts.warnAt ?? 25;
let p = opts.value ?? 62;
@@ -2844,7 +2844,7 @@ GW.battCells = function (host, opts = {}) {
/* R01 moving-coil VU — pivot below the face, authentic nonlinear dB scale;
set(t) positions instantly (0..1.02); ballistics belong to the signal owner */
-GW.mcVu = function (host, opts = {}) {
+DUPRE.mcVu = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg', 150, 96);
const cx = 75, cy = 112, sweep = t => -43 + t * 86;
@@ -2896,7 +2896,7 @@ GW.mcVu = function (host, opts = {}) {
};
/* R07 round panel meter — porthole bezel, same VU law as R01; drag up/down */
-GW.roundMeter = function (host, opts = {}) {
+DUPRE.roundMeter = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg drag', 110, 104);
const cx = 55, fy = 54, py = 74, sweep = t => -40 + t * 80;
@@ -2948,7 +2948,7 @@ GW.roundMeter = function (host, opts = {}) {
};
/* R08 chrome MIN/MAX indicator — dark pointer over a brushed dome */
-GW.chromeMinMax = function (host, opts = {}) {
+DUPRE.chromeMinMax = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg drag', 110, 104);
const cx = 55, cy = 58;
@@ -2991,7 +2991,7 @@ GW.chromeMinMax = function (host, opts = {}) {
};
/* R09 black-face aviation gauge — zone arcs per the airspeed-indicator scheme */
-GW.aviationGauge = function (host, opts = {}) {
+DUPRE.aviationGauge = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg drag', 110, 104);
const cx = 55, cy = 52, sweep = t => -135 + t * 270;
@@ -3045,7 +3045,7 @@ GW.aviationGauge = function (host, opts = {}) {
};
/* R13 edgewise strip meter — compressed log scale, the bar rides the edge */
-GW.edgeMeter = function (host, opts = {}) {
+DUPRE.edgeMeter = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const SCALE = [[0, .08], [3, .26], [6, .44], [12, .62], [20, .78], [40, .94]];
const s = stageSvg(host, 'rsvg drag', 70, 130);
@@ -3086,9 +3086,9 @@ GW.edgeMeter = function (host, opts = {}) {
return { el: s, get: () => frac, set };
};
-/* R17 round CRT scope — pale phosphor face; the trace is the widget's own
+/* R17 round CRT scope — pale phosphor face; the trace is the instrument's own
animation, so it lives here with its reduced-motion gate */
-GW.roundCrt = function (host, opts = {}) {
+DUPRE.roundCrt = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg', 110, 104);
const cx = 55, cy = 52;
@@ -3098,7 +3098,7 @@ GW.roundCrt = function (host, opts = {}) {
const fl = svgEl(d, 'filter', { id: 'avGlow', x: '-60%', y: '-60%', width: '220%', height: '220%' });
svgEl(fl, 'feGaussianBlur', { in: 'SourceGraphic', stdDeviation: 1.4 });
});
- /* the face gradient reads screen-family vars from this widget's subtree,
+ /* the face gradient reads screen-family vars from this instrument's subtree,
so it must live in the local defs, not the shared def sink */
const defs = svgEl(s, 'defs', {});
const ph = svgEl(defs, 'radialGradient', { id: faceId, cx: '50%', cy: '44%', r: '70%' });
@@ -3139,7 +3139,7 @@ GW.roundCrt = function (host, opts = {}) {
};
/* R43 attitude indicator — sky/ground roll+shift behind a fixed miniature aircraft; 2D drag */
-GW.attitudeIndicator = function (host, opts = {}) {
+DUPRE.attitudeIndicator = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg', 130, 130);
const cx = 65, cy = 65;
@@ -3194,8 +3194,8 @@ GW.attitudeIndicator = function (host, opts = {}) {
};
/* R44 heading bug + servo needle — drag parks the command; the needle chases
- with honest servo lag (widget-owned animation, reduced-motion gated) */
-GW.headingBug = function (host, opts = {}) {
+ with honest servo lag (instrument-owned animation, reduced-motion gated) */
+DUPRE.headingBug = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg', 130, 130);
const cx = 65, cy = 65;
@@ -3245,8 +3245,8 @@ GW.headingBug = function (host, opts = {}) {
};
/* R53 circular chart recorder — a day per revolution; the pen draws the cycle
- (widget-owned clock, reduced-motion paints the full day once); click for fresh paper */
-GW.chartRecorder = function (host, opts = {}) {
+ (instrument-owned clock, reduced-motion paints the full day once); click for fresh paper */
+DUPRE.chartRecorder = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 130, 130);
const cx = 65, cy = 65;
@@ -3296,7 +3296,7 @@ GW.chartRecorder = function (host, opts = {}) {
};
/* R54 vertical tape instrument — the scale scrolls behind a fixed index; drag to drive */
-GW.verticalTape = function (host, opts = {}) {
+DUPRE.verticalTape = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg', 70, 130);
const CX = 38, CY = 65;
@@ -3338,7 +3338,7 @@ GW.verticalTape = function (host, opts = {}) {
};
/* R55 twin-needle gauge — mirrored half-scales, one hub, two independent needles */
-GW.twinNeedle = function (host, opts = {}) {
+DUPRE.twinNeedle = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg', 120, 110);
const cx = 60, cy = 62, R = 44;
@@ -3406,7 +3406,7 @@ GW.twinNeedle = function (host, opts = {}) {
};
/* R56 comfort-zone crossed needles — temp and humidity cross over printed verdicts */
-GW.comfortMeter = function (host, opts = {}) {
+DUPRE.comfortMeter = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg', 130, 122);
const cx = 65, cy = 60;
@@ -3502,7 +3502,7 @@ GW.comfortMeter = function (host, opts = {}) {
/* ================= indicators & readouts ================= */
/* 18 status lamps — one lamp per health state; click any lamp to cycle it */
-GW.statusLamps = function (host, opts = {}) {
+DUPRE.statusLamps = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const CLS = ['lamp', 'lamp gold', 'lamp red', 'lamp off', 'lamp busy'];
const NAMES = ['ok', 'engaged', 'fault', 'off', 'busy'];
@@ -3525,7 +3525,7 @@ GW.statusLamps = function (host, opts = {}) {
};
/* 19 badges — labelled flags; click a badge to cycle its variant */
-GW.badges = function (host, opts = {}) {
+DUPRE.badges = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const CLS = ['badge', 'badge red', 'badge ghost'];
const items = opts.items || [['TUNNEL', 0], ['LOW BATT', 1], ['2.4G', 2]];
@@ -3550,7 +3550,7 @@ GW.badges = function (host, opts = {}) {
/* 20 tabular readout — mm:ss countdown; builder owns the state, the page
drives tick() on its own clock (tick contract); click pauses/resumes */
-GW.tabularReadout = function (host, opts = {}) {
+DUPRE.tabularReadout = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const wrap = document.createElement('div'); wrap.style.textAlign = 'center';
wrap.innerHTML = '<div class="readout"></div><span class="u"></span>';
@@ -3569,7 +3569,7 @@ GW.tabularReadout = function (host, opts = {}) {
};
/* 21 engraved label — hairline-flanked caps label with a count; click bumps 1-9 */
-GW.engravedLabel = function (host, opts = {}) {
+DUPRE.engravedLabel = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const e = document.createElement('span'); e.className = 'engrave';
e.append(opts.label || 'outputs');
@@ -3584,7 +3584,7 @@ GW.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 */
-GW.outputWell = function (host, opts = {}) {
+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']];
@@ -3605,7 +3605,7 @@ GW.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 */
-GW.toast = function (host, opts = {}) {
+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';
@@ -3622,7 +3622,7 @@ GW.toast = function (host, opts = {}) {
};
/* 26 nixie tubes — one lit numeral per tube, leading zeros dark; click increments */
-GW.nixie = function (host, opts = {}) {
+DUPRE.nixie = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const digits = opts.digits || 2;
const max = Math.pow(10, digits);
@@ -3671,8 +3671,8 @@ GW.nixie = function (host, opts = {}) {
setStyle(axis, name), chars, flapMs()/setFlapMs(ms) (base flap
rate, clamped 20-400), onSettle(cb) — cb(reading) fires once per
command when every cell has arrived (dwell hook).
- CSS lives in the "split-flap" block of GW_CSS; no other styles involved. */
-GW.splitFlap = function (host, opts = {}) {
+ CSS lives in the "split-flap" block of DUPRE_CSS; no other styles involved. */
+DUPRE.splitFlap = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const words = opts.words || ['DNS ', 'LINK', 'SYNC', 'WIFI', 'SCAN'];
const animate = opts.animate !== undefined ? !!opts.animate : true;
@@ -3802,7 +3802,7 @@ GW.splitFlap = function (host, opts = {}) {
// construction axes (skin, font): swap this axis's classes per STYLES.
// Polarity and typeface are construction, not accent — no colour policy.
const setStyle = (axis, name) => {
- const ax = GW.splitFlap.STYLES[axis]; const o = ax && ax[name];
+ const ax = DUPRE.splitFlap.STYLES[axis]; const o = ax && ax[name];
if (!o) return;
Object.values(ax).forEach(v => { if (v.cls) f.classList.remove(v.cls); });
if (o.cls) f.classList.add(o.cls);
@@ -3818,7 +3818,7 @@ GW.splitFlap = function (host, opts = {}) {
flapMs: () => flapMs, setFlapMs: ms => { flapMs = clampMs(ms); },
};
};
-GW.splitFlap.STYLES = {
+DUPRE.splitFlap.STYLES = {
// board polarity + ink: dark board with the kit's cream ink (default),
// dark board with true-white ink, or the inverse ivory board
// dot: the board colour; the gallery card letters each chip in the skin's
@@ -3838,7 +3838,7 @@ GW.splitFlap.STYLES = {
/* N21 seven-segment countdown — mm:ss in lit segments; the page drives tick();
click adds a minute */
-GW.sevenSeg = function (host, opts = {}) {
+DUPRE.sevenSeg = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const sv = document.createElement('span'); sv.className = 'seven'; host.appendChild(sv);
let secs;
@@ -3854,7 +3854,7 @@ GW.sevenSeg = function (host, opts = {}) {
};
/* N22 VFD marquee — teal dot-matrix scroll; the page drives tick(); click cycles the message */
-GW.vfdMarquee = function (host, opts = {}) {
+DUPRE.vfdMarquee = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const msgs = opts.msgs || ['ARCHSETUP · NET OK · BT 2 · SND 62%', 'WIFI @Hyatt · 300 Mbps · VPN UP', 'BATTERY 84% · DISK 61% · TEMP 47C'];
const W = opts.width || 176;
@@ -3876,7 +3876,7 @@ GW.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 */
-GW.annunciator = function (host, opts = {}) {
+DUPRE.annunciator = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const CLS = ['acell', 'acell warn', 'acell fault'];
const NAMES = ['ok', 'warn', 'fault'];
@@ -3923,7 +3923,7 @@ GW.annunciator = function (host, opts = {}) {
};
/* N24 jewel pilot lamps — faceted bezel indicators; click cycles red · amber · green · dark */
-GW.jewels = function (host, opts = {}) {
+DUPRE.jewels = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const cols = ['var(--jewel-r)', 'var(--jewel-a)', 'var(--jewel-g)'];
const NAMES = ['red', 'amber', 'green'];
@@ -3947,7 +3947,7 @@ GW.jewels = function (host, opts = {}) {
};
/* N25 tape counter — odometer wheels; set(total) rolls each wheel to its digit */
-GW.tapeCounter = function (host, opts = {}) {
+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);
@@ -3971,7 +3971,7 @@ GW.tapeCounter = function (host, opts = {}) {
/* N26 analog clock — engraved ticks + three hands; the page owns the time source
and drives set(h, m, s); silent until the first set, like the live original */
-GW.analogClock = function (host, opts = {}) {
+DUPRE.analogClock = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const cl = document.createElement('span'); cl.className = 'clock';
for (let i = 0; i < 12; i++) { const t = document.createElement('span'); t.className = 'tk'; t.style.transform = `rotate(${i * 30}deg)`; cl.appendChild(t); }
@@ -3989,7 +3989,7 @@ GW.analogClock = function (host, opts = {}) {
/* N27 frequency-dial scale — printed log axis, marks crowd low and spread high;
drag the pointer to tune */
-GW.freqScale = function (host, opts = {}) {
+DUPRE.freqScale = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const W = opts.width || 184;
const marks = opts.marks || [0.5, 1, 2, 3, 5, 7, 10, 14, 20];
@@ -4021,7 +4021,7 @@ GW.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 */
-GW.patchBay = function (host, opts = {}) {
+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';
@@ -4065,7 +4065,7 @@ GW.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) */
-GW.dataMatrix = function (host, opts = {}) {
+DUPRE.dataMatrix = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const pages = opts.pages || [
['SYS', () => ['CPU 42 MEM 61', 'DSK 58 TMP 47C', 'ARCH 6.18 LTS']],
@@ -4081,7 +4081,7 @@ GW.dataMatrix = function (host, opts = {}) {
const fl = svgEl(d, 'filter', { id: 'dmxGlow', x: '-30%', y: '-30%', width: '160%', height: '160%' });
svgEl(fl, 'feGaussianBlur', { in: 'SourceGraphic', stdDeviation: 1.1 });
});
- /* the background gradient reads screen-family vars from this widget's subtree,
+ /* the background gradient reads screen-family vars from this instrument's subtree,
so it lives in the local defs, not the shared def sink */
const bgId = uid('dmxBg');
const defs = svgEl(s, 'defs', {});
@@ -4117,7 +4117,7 @@ GW.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 */
-GW.warningFlag = function (host, opts = {}) {
+DUPRE.warningFlag = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 120, 64);
def('barber', d => {
@@ -4146,7 +4146,7 @@ GW.warningFlag = function (host, opts = {}) {
/* R25 fourteen-segment display — the starburst alphanumeric that spells words;
click cycles the word */
-GW.seg14 = function (host, opts = {}) {
+DUPRE.seg14 = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const words = opts.words || ['ZOOM', 'ECHO', 'TAPE', 'MOOD', 'HALL', 'COMP'];
const MAP = {
@@ -4204,7 +4204,7 @@ GW.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 */
-GW.responseGraph = function (host, opts = {}) {
+DUPRE.responseGraph = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg', 190, 110);
const RG = { x0: 30, x1: 182, y0: 8, y1: 86, fLo: Math.log10(32), fHi: Math.log10(16000), db: 12 };
@@ -4272,7 +4272,7 @@ GW.responseGraph = function (host, opts = {}) {
/* R30 telegraph indicator — the pointer names the active state on labeled
sectors, engine-telegraph style; click steps the state */
-GW.telegraphIndicator = function (host, opts = {}) {
+DUPRE.telegraphIndicator = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const states = opts.states || ['OK', 'TEST', 'BUSY', 'STOP', 'ATTN', 'FAULT'];
const s = stageSvg(host, 'rsvg press', 110, 110), cx = 55, cy = 55, N = states.length;
@@ -4305,7 +4305,7 @@ GW.telegraphIndicator = function (host, opts = {}) {
/* R31 radar sweep — the beam circles the bearing ring on the builder's own
clock (reduced-motion gated), contacts bloom as it passes; click marks the bearing */
-GW.radarSweep = function (host, opts = {}) {
+DUPRE.radarSweep = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const contacts = opts.contacts || [[60, .55], [205, .7], [318, .4]];
const s = stageSvg(host, 'rsvg press', 130, 130), cx = 65, cy = 65;
@@ -4379,7 +4379,7 @@ GW.radarSweep = function (host, opts = {}) {
/* R35 day-date disc calendar — coaxial printed discs rotate so today reads
under the fixed hand; starts on today; click rolls midnight forward */
-GW.dayDateCal = function (host, opts = {}) {
+DUPRE.dayDateCal = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const DAYS = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'];
const s = stageSvg(host, 'rsvg press', 130, 130), cx = 65, cy = 65, HAND = 135;
@@ -4435,7 +4435,7 @@ GW.dayDateCal = function (host, opts = {}) {
/* R36 LED dot matrix — 8x8 paintable bitmap behind a tinted window; click dots
to paint; starts on the reference K */
-GW.dotMatrix = function (host, opts = {}) {
+DUPRE.dotMatrix = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const N = 8, STEP = 11, X0 = 10.5, Y0 = 10.5;
const glyph = opts.glyph || ['10000100', '10001000', '10010000', '11100000', '10100000', '10010000', '10001000', '10000100'];
@@ -4464,7 +4464,7 @@ GW.dotMatrix = function (host, opts = {}) {
/* R45 flip-disc tile array — bistable mechanical pixels; scaleX flip with the
color swap at the midpoint; click a disc to flip it */
-GW.flipDisc = function (host, opts = {}) {
+DUPRE.flipDisc = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const COLS = 7, ROWS = 5, STEP = 14, X0 = 13, Y0 = 13;
const glyph = opts.glyph || ['0000100', '0000010', '1111111', '0000010', '0000100']; /* arrow right */
@@ -4493,7 +4493,7 @@ GW.flipDisc = function (host, opts = {}) {
/* R46 dekatron counting ring — each click pulses the neon glow one cathode
around; the wrap flashes the carry dot */
-GW.dekatron = function (host, opts = {}) {
+DUPRE.dekatron = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 120, 120), cx = 60, cy = 60;
svgEl(s, 'circle', { cx, cy, r: 52, fill: '#171310', stroke: '#060505', 'stroke-width': 2 });
@@ -4538,7 +4538,7 @@ GW.dekatron = function (host, opts = {}) {
/* R47 landing gear indicator — three greens or nothing; the lever cycles
down → transit (amber pulse) → up */
-GW.gearIndicator = function (host, opts = {}) {
+DUPRE.gearIndicator = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 140, 110);
svgEl(s, 'rect', { x: 2, y: 2, width: 136, height: 106, rx: 9, fill: '#1c1916', stroke: '#060505', 'stroke-width': 1.5 });
@@ -4589,7 +4589,7 @@ GW.gearIndicator = function (host, opts = {}) {
/* R52 blinkenlights front panel — address/data lamps ripple with a live
pseudo-PC (builder-owned clock, reduced-motion gated) that folds in the
switch register; flip the SR bits and the pattern changes */
-GW.blinkenlights = function (host, opts = {}) {
+DUPRE.blinkenlights = function (host, opts = {}) {
const onChange = opts.onChange || noop;
const s = stageSvg(host, 'rsvg press', 170, 100);
svgEl(s, 'rect', { x: 2, y: 2, width: 166, height: 96, rx: 8, fill: '#3a1420', stroke: '#060505', 'stroke-width': 1.5 });
@@ -4637,9 +4637,9 @@ GW.blinkenlights = function (host, opts = {}) {
return { el: s, get: () => sr, tick };
};
-/* ---- widget CSS: injected once, grows as builders move in ---- */
-/* widget-internal CSS (moved from the gallery <style> block; gallery keeps page furniture) */
-const GW_CSS = `
+/* ---- instrument CSS: injected once, grows as builders move in ---- */
+/* instrument-internal CSS (moved from the gallery <style> block; gallery keeps page furniture) */
+const DUPRE_CSS = `
/* ---- shared primitives ---- */
.lamp{width:9px;height:9px;border-radius:50%;background:var(--pass);box-shadow:0 0 6px 1px rgba(116,147,47,.55)}
.lamp.gold{background:var(--gold);box-shadow:0 0 6px 1px rgba(var(--glow-lo),.6)}
@@ -5161,7 +5161,7 @@ const GW_CSS = `
.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}
-/* ===== reference-batch (R) widgets — SVG-first, after period hardware ===== */
+/* ===== reference-batch (R) instruments — SVG-first, after period hardware ===== */
.rsvg{display:block}
.rsvg.drag{cursor:ns-resize;touch-action:none}
.rsvg.press{cursor:pointer}
@@ -5171,44 +5171,44 @@ const GW_CSS = `
.reel.spin{animation:reelspin 2.6s linear infinite}
`;
function ensureCss() {
- if (document.getElementById('gw-css') || !GW_CSS) return;
- const st = document.createElement('style'); st.id = 'gw-css'; st.textContent = GW_CSS;
+ if (document.getElementById('dupre-css') || !DUPRE_CSS) return;
+ const st = document.createElement('style'); st.id = 'dupre-css'; st.textContent = DUPRE_CSS;
document.head.appendChild(st);
}
if (document.head) ensureCss();
else document.addEventListener('DOMContentLoaded', ensureCss);
-/* Policy classification (see GW.POLICIES). This is the colour pass's worklist made
+/* Policy classification (see DUPRE.POLICIES). This is the colour pass's worklist made
explicit: every card gets a record here as we review it — the kind it's bound
by, why in its own terms, and the range it may vary and stay authentic. This
round covers the cards already touched; the rest are unclassified on purpose,
the review still to do card by card, not a memory dump to fill now. Assigned
after the builders exist, in one place, so it reads as a catalogue. */
-GW.slideToggle.POLICY = { kind: 'accent',
- why: 'On means good in one panel and a fault in the next, so the lit colour is the consumer’s claim, not the widget’s.',
+DUPRE.slideToggle.POLICY = { kind: 'accent',
+ why: 'On means good in one panel and a fault in the next, so the lit colour is the consumer’s claim, not the instrument’s.',
authentic: 'The on-colour from the accent family (red, amber, green, white, vfd), plus the pill and thumb finishes. The switch form stays.' };
-GW.segmented.POLICY = { kind: 'accent',
+DUPRE.segmented.POLICY = { kind: 'accent',
why: 'The active segment signals a mode whose meaning depends on the panel it sits in.',
authentic: 'The accent colour of the active segment. The segment count and labels are the deployment’s, not a colour choice.' };
-GW.chipToggle.POLICY = { kind: 'accent',
+DUPRE.chipToggle.POLICY = { kind: 'accent',
why: 'A filter chip’s on-state reads as good, warning or fault entirely by where it is used.',
authentic: 'The lit colour from the accent family. The chip keeps its inline weight and dotted underline.' };
-GW.dataMatrix.POLICY = { kind: 'screen',
+DUPRE.dataMatrix.POLICY = { kind: 'screen',
why: 'LED and LCD dot-matrix modules were sold in several emitter colours, so no one colour is definitive.',
authentic: 'The screen family (amber, green, red, blue, vfd, white) — each a panel that was actually built. Not an arbitrary hue.' };
-GW.roundCrt.POLICY = { kind: 'screen',
+DUPRE.roundCrt.POLICY = { kind: 'screen',
why: 'CRT phosphors were manufactured in more than one colour (P1 green, P3 amber, white), so the trace colour is a real choice.',
authentic: 'The screen family, limited to phosphors that existed. The face tint follows the phosphor.' };
-GW.waveRegion.POLICY = { kind: 'screen',
+DUPRE.waveRegion.POLICY = { kind: 'screen',
why: 'A backlit editor LCD was made in several tints; the ink colour is the panel’s, not fixed.',
authentic: 'The screen family. The waveform and region handles recolour with the screen, staying legible on it.' };
-GW.radarSweep.POLICY = { kind: 'screen',
+DUPRE.radarSweep.POLICY = { kind: 'screen',
why: 'PPI radar scopes ran amber and green phosphors both; neither is the one true colour.',
authentic: 'The screen family, phosphors that shipped. The sweep and afterglow track the chosen screen.' };
-GW.abcKeypad.POLICY = { kind: 'screen',
+DUPRE.abcKeypad.POLICY = { kind: 'screen',
why: 'The entry window is a screen like any other; its phosphor was made in several colours. (Mixed card: the keys are fixed-function and not a colour choice.)',
authentic: 'The window’s screen family. The keycap colours are functional and stay put.' };
-Object.assign(GW, { SVGNS, svgEl, polar, dragX, dragY, dragDelta, SEG, seg7, buildBars, VUDB, vuDb, SCREEN_FAMS });
-window.GW = GW;
+Object.assign(DUPRE, { SVGNS, svgEl, polar, dragX, dragY, dragDelta, SEG, seg7, buildBars, VUDB, vuDb, SCREEN_FAMS });
+window.DUPRE = DUPRE;
})();