aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes/widgets.js
diff options
context:
space:
mode:
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;
})();