diff options
Diffstat (limited to 'docs/prototypes/panel-widget-gallery.html')
| -rw-r--r-- | docs/prototypes/panel-widget-gallery.html | 1616 |
1 files changed, 1616 insertions, 0 deletions
diff --git a/docs/prototypes/panel-widget-gallery.html b/docs/prototypes/panel-widget-gallery.html new file mode 100644 index 0000000..9740c46 --- /dev/null +++ b/docs/prototypes/panel-widget-gallery.html @@ -0,0 +1,1616 @@ +<!doctype html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>Panel widget gallery — dupre instrument console (interactive)</title> +<style> +:root{ +/* @tokens:start */ + /* generated from tokens.json by gen_tokens.py — edit tokens.json, then run: python3 gen_tokens.py */ + --ground:#151311; + --panel:#100f0f; + --well:#0a0c0d; + --raise:#1a1917; + --silver:#bfc4d0; + --cream:#f3e7c5; + --steel:#969385; + --dim:#7c838a; + --slate:#424f5e; + --slate-hi:#54677d; + --wash:#2c2f32; + --pass:#74932f; + --fail:#cb6b4d; + --phos:#7fe0a0; + --phos-dim:#1c3626; + --vfd:#63e6c8; + --sevgrn:#57d357; + --sevred:#e2543f; + --sevoff:#1a1613; + --jewel-r:#ff5b45; + --jewel-a:#ffb43a; + --jewel-g:#6fce33; + --gold:#e2a038; + --gold-hi:#ffbe54; + --amber-grad-top:#f2c76a; + --amber-grad-mid:#d29638; + --amber-grad-bot:#8f671f; + --amber-edge:#7d5c16; + --amber-warn:#f0b552; + --glow-hi:255,190,84; + --glow-lo:226,160,56; + --mono:"BerkeleyMono Nerd Font","Berkeley Mono",monospace; + --pulse-rate:1s; +/* @tokens:end */ +} +*{box-sizing:border-box;margin:0;padding:0} +html{background:var(--ground);font-size:130%} +body{font-family:var(--mono);color:var(--silver);padding:2.4rem 2rem 5rem;line-height:1.45; + background:radial-gradient(1200px 600px at 70% -10%,#1c1915 0%,transparent 60%),var(--ground)} +.wrap{max-width:1320px;margin:0 auto} +.eyebrow{color:var(--steel);font-size:.72rem;letter-spacing:.28em;text-transform:uppercase} +h1{color:var(--gold);font-size:1.5rem;margin:.35rem 0 .4rem} +.masthead p{color:var(--dim);font-size:.86rem;max-width:86ch} +.masthead p b{color:var(--silver)} +.szbar{display:flex;align-items:center;gap:8px;margin-top:.8rem} +.szbar .lab{color:var(--steel);font-size:.68rem;letter-spacing:.22em;text-transform:uppercase;margin-right:4px} + +/* masthead row: description left, index panel right */ +.mastrow{display:flex;gap:28px;align-items:flex-start;justify-content:space-between;flex-wrap:wrap} +.toc{position:relative;flex:0 0 auto;min-width:220px;padding:14px 20px 13px;border-radius:12px; + background:linear-gradient(180deg,var(--raise),var(--panel));border:1px solid #262320; + box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 6px 14px rgba(0,0,0,.4)} +.toc .tt{color:var(--steel);font-size:.66rem;letter-spacing:.26em;text-transform:uppercase; + margin-bottom:7px;display:flex;align-items:center;gap:10px} +.toc .tt::after{content:"";height:1px;background:var(--wash);flex:1;min-width:24px} +.toc a{display:block;color:var(--dim);text-decoration:none;font-size:.8rem;letter-spacing:.04em;padding:3.5px 0} +.toc a::before{content:"▸ ";color:var(--steel);font-size:.72em} +.toc a:hover{color:var(--gold-hi)} +.toc .scr{position:absolute;width:5px;height:5px;border-radius:50%; + background:radial-gradient(circle at 35% 30%,#524d43,#161310);box-shadow:0 .5px 0 rgba(255,255,255,.08)} +html{scroll-behavior:smooth} +/* size toggle scales the widget stage only; card text stays at its own (slightly raised) size */ +body[data-size="2"] .stagew{zoom:1.7} +body[data-size="3"] .stagew{zoom:2.4} +/* intrinsically tiny widgets get an extra bump so they do not float in an empty stage */ +.stagew.boost{zoom:1.5} +body[data-size="2"] .stagew.boost{zoom:2.55} +body[data-size="3"] .stagew.boost{zoom:3.6} +body[data-size="2"] .grid{grid-template-columns:repeat(auto-fill,minmax(380px,1fr))} +body[data-size="3"] .grid{grid-template-columns:repeat(auto-fill,minmax(520px,1fr))} +body[data-size="2"] .wrap,body[data-size="3"] .wrap{max-width:none} + +.secnote{color:var(--dim);font-size:.74rem;margin-top:.5rem;max-width:86ch} +.palgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(176px,1fr));gap:10px;margin-top:1rem} +.swatch{background:linear-gradient(180deg,var(--raise),var(--panel));border:1px solid #262320;border-radius:9px; + padding:9px;display:flex;flex-direction:column;gap:6px} +.swatch .sw{height:44px;border-radius:6px;border:1px solid rgba(255,255,255,.08)} +.swatch .sw.txt{display:flex;align-items:center;justify-content:center;color:var(--steel);font-size:.62rem; + background:var(--well);padding:0 6px;text-align:center;overflow:hidden} +.swatch .swn{color:var(--cream);font-size:.8rem} +.swatch .swv{color:var(--dim);font-size:.72rem;line-height:1.35} +.palhead{grid-column:1/-1;color:var(--steel);font-size:.72rem;letter-spacing:.22em;text-transform:uppercase; + margin-top:.8rem;display:flex;align-items:center;gap:12px} +.palhead::after{content:"";height:1px;background:var(--wash);flex:1} +h2{color:var(--steel);font-size:.74rem;letter-spacing:.24em;text-transform:uppercase; + margin:2.2rem 0 .2rem;display:flex;align-items:center;gap:12px} +h2::after{content:"";height:1px;background:var(--wash);flex:1} + +.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(232px,1fr));gap:14px;margin-top:1rem} +.card{background:linear-gradient(180deg,var(--raise),var(--panel));border:1px solid #262320;border-radius:12px; + padding:13px 14px 12px;display:flex;flex-direction:column;gap:9px; + box-shadow:inset 0 1px 0 rgba(255,255,255,.04),0 6px 14px rgba(0,0,0,.4)} +.wname{color:var(--cream);font-size:.95rem;font-weight:700;display:flex;align-items:center;gap:8px} +.wname .no{color:var(--panel);background:var(--gold);border-radius:4px;font-size:.7rem;padding:0 5px;font-weight:400} +/* validation lamp: off = not done, amber = in progress, green = done; click cycles, state persists */ +.vlamp{margin-left:auto;flex:0 0 auto;width:10px;height:10px;border-radius:50%;cursor:pointer; + background:#221f1b;border:1px solid #33302b} +.vlamp[data-v="amber"],.vtally .vdot[data-v="amber"],#vaudit .vdot[data-v="amber"]{background:var(--gold);border-color:#7d5c16;box-shadow:0 0 6px 1px rgba(var(--glow-lo),.6)} +.vlamp[data-v="green"],.vtally .vdot[data-v="green"],#vaudit .vdot[data-v="green"]{background:var(--pass);border-color:#4a5c22;box-shadow:0 0 6px 1px rgba(116,147,47,.6)} +/* audit stepper pill: docks bottom-right while stepping through one state's cards */ +#vaudit{position:fixed;right:18px;bottom:16px;z-index:60;display:flex;align-items:center;gap:7px; + padding:8px 13px;border-radius:20px;background:var(--raise);border:1px solid var(--wash); + color:var(--cream);font-size:.78rem;letter-spacing:.05em;cursor:pointer;user-select:none; + box-shadow:0 4px 14px rgba(0,0,0,.5)} +#vaudit .vdot{flex:0 0 auto;width:8px;height:8px;border-radius:50%;background:#221f1b;border:1px solid #33302b} +#vaudit .vx{margin-left:6px;color:var(--steel);padding:0 2px} +#vaudit .vx:hover{color:var(--fail)} +/* validation tally in the index: .vdot mirrors the lamp look but must NOT match + .vlamp — the counter counts .vlamp nodes */ +.vtally{margin-top:2px} +.vtally .vrow{display:flex;align-items:center;gap:7px;color:var(--dim);font-size:.75rem; + letter-spacing:.04em;padding:2.5px 0} +.vtally .vrow[data-v]{cursor:pointer} +.vtally .vrow[data-v]:hover{color:var(--gold-hi)} +.vtally .vdot{flex:0 0 auto;width:8px;height:8px;border-radius:50%; + background:#221f1b;border:1px solid #33302b} +.vtally .vn{margin-left:auto;color:var(--cream);font-variant-numeric:tabular-nums} +.vtally .vtot{border-top:1px solid var(--wash);margin-top:3px;padding-top:4.5px;color:var(--steel)} +.stagew{background:var(--well);border:1px solid #201d17;border-radius:9px;padding:14px 12px; + min-height:78px;display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap} +.wrd{color:var(--gold-hi);font-size:.8rem;letter-spacing:.06em;font-variant-numeric:tabular-nums; + min-height:.95rem;display:flex;align-items:center;gap:6px; + border-top:1px solid var(--wash);padding-top:9px;margin-top:2px} +.wrd::before{content:"▸";color:var(--steel);font-size:.7rem} +.opts{display:flex;flex-direction:column;gap:5px} +.opts:empty{display:none} +.wnote{color:var(--dim);font-size:.85rem;line-height:1.45; + border-top:1px solid var(--wash);padding-top:9px;margin-top:2px} +.wnote b{color:var(--steel);font-weight:400} + +/* per-card spec sheet (collapsible) */ +.winfo{border-top:1px dashed var(--wash);padding-top:6px;margin-top:2px} +.winfo summary{color:var(--steel);font-size:.74rem;letter-spacing:.18em;text-transform:uppercase; + cursor:pointer;list-style:none;user-select:none} +.winfo summary::-webkit-details-marker{display:none} +.winfo summary::before{content:"▸ "} +.winfo[open] summary::before{content:"▾ "} +.igrid{display:grid;grid-template-columns:max-content 1fr;gap:4px 10px;margin-top:7px} +.igrid .ik{color:var(--steel);font-size:.72rem;letter-spacing:.08em;text-transform:uppercase;padding-top:1px} +.igrid .iv{color:var(--dim);font-size:.82rem;line-height:1.45} +.igrid .iv b{color:var(--silver);font-weight:400} +.igrid .iv a{color:var(--gold);text-decoration:none;border-bottom:1px dotted var(--gold)} +.igrid .iv a:hover{color:var(--gold-hi);border-color:var(--gold-hi)} +.card a.xref{color:var(--gold);text-decoration:none;border-bottom:1px dotted var(--gold)} +.card a.xref:hover{color:var(--gold-hi);border-color:var(--gold-hi)} +.card:target{border-color:var(--gold);box-shadow:inset 0 1px 0 rgba(255,255,255,.04),0 0 0 1px var(--gold),0 6px 14px rgba(0,0,0,.4)} + +/* option chips: each group is an encircled capsule (label + choices); groups flow with spacing */ +.famchips{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:1px} +.fgroup{display:inline-flex;align-items:center;gap:6px;padding:3px 9px 3px 8px; + border:1px solid #2e2b26;border-radius:10px;background:rgba(255,255,255,.015)} +.famchips .lab{color:var(--steel);font-size:.6rem;letter-spacing:.14em;text-transform:uppercase;margin-right:2px} +.famchips .fc{width:13px;height:13px;border-radius:50%;cursor:pointer; + border:1px solid rgba(255,255,255,.22);box-shadow:0 1px 3px rgba(0,0,0,.5)} +.famchips .fc.on{outline:2px solid var(--silver);outline-offset:1px} + +@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}} +</style> +</head> +<body data-size="2"> +<div class="wrap"> +<header class="masthead"> + <div class="eyebrow">archsetup · dupre panel family · live kit</div> + <h1>Widget gallery — the instrument-console kit, fully driveable</h1> + <div class="mastrow"> + <div> + <p>Every control + display idiom in the dupre faceplate language, merged into one live console — the base kit, + the candidate batch, and the growing reference batch (R: modeled on period hardware), all rendering from + the same tokens the net / bt / sound panels use. + <b>Controls</b> take input; <b>meters & gauges</b> show a live analog value; <b>indicators & + readouts</b> show state or a number. Every widget is operable: drag the faders, knobs and gauges; click the + switches, keys and lamps. Each card carries a live readout that tracks what you do. The trace displays run + a live signal; the pointer types drive from your drag.</p> + <div class="szbar"><span class="lab">widget size</span> + <button class="key" data-sz="1">S</button> + <button class="key on" data-sz="2">M</button> + <button class="key" data-sz="3">L</button> + </div> + </div> + <nav class="toc"> + <span class="scr" style="top:6px;left:6px"></span><span class="scr" style="top:6px;right:6px"></span> + <span class="scr" style="bottom:6px;left:6px"></span><span class="scr" style="bottom:6px;right:6px"></span> + <div class="tt">Index</div> + <a href="#sec-controls">Controls</a> + <a href="#sec-meters">Meters & gauges</a> + <a href="#sec-indicators">Indicators & readouts</a> + <a href="#sec-palette">Palette</a> + <div class="tt" style="margin-top:11px">Validation</div> + <div class="vtally" id="vtally"></div> + </nav> + </div> +</header> + +<h2 id="sec-controls">Controls — take input</h2> +<div class="grid" id="controls"></div> + +<h2 id="sec-meters">Meters & gauges — live analog value</h2> +<div class="grid" id="meters"></div> + +<h2 id="sec-indicators">Indicators & readouts — state or number</h2> +<div class="grid" id="indicators"></div> + +<h2 id="sec-palette">Palette — the instrument colors</h2> +<p class="secnote">Every named color the instruments are built from — markers, hands, faces, lamps, phosphors, + materials — with where it appears. Page chrome (background, body text) is deliberately absent.</p> +<div class="palgrid" id="palette"></div> + +</div> +<script src="widgets.js"></script> +<script> +const $ = id => document.getElementById(id); +const {svgEl,polar,dragX,dragY,dragDelta,seg7,buildBars,VUDB,vuDb,SCREEN_FAMS}=GW; +const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches; +document.querySelectorAll('.szbar .key').forEach(b=>b.addEventListener('click',()=>{ + document.body.dataset.size=b.dataset.sz; + localStorage.setItem('gv-size',b.dataset.sz); + document.querySelectorAll('.szbar .key').forEach(k=>k.classList.toggle('on',k===b)); +})); +(function(){const s=localStorage.getItem('gv-size'); + if(!s||!/^[123]$/.test(s))return; + document.body.dataset.size=s; + document.querySelectorAll('.szbar .key').forEach(k=>k.classList.toggle('on',k.dataset.sz===s));})(); +/* boost the intrinsically tiny widgets (audit: content under 10% of the stage) */ +const BOOST=['01','07','08','12','13','N14','18']; + +/* ---- palette: the named instrument colors, grouped by role. No codes shown — names and uses. ---- */ +const PALETTE=[ + ['Materials',[ + ['Brass','#b8944a','comfort-meter bezel and knurl'], + ['Copper','#ad7a43','knife-switch blades, jaws and hinge'], + ['Chrome silver','var(--silver)','needles, ball lever, fader caps, chrome handles'], + ['Machined steel','var(--steel)','engraved captions, scale furniture, printed panel lines'], + ['Aluminum','#c9c4b8','timer hub, lever shafts, counter bezels'], + ['Rubber & bakelite','#2c2824','knob bodies, rocker pad, stomp switch'], + ]], + ['Faces & inks',[ + ['Cream dial','var(--cream)','tick marks, scale numerals, dial faces, flag windows'], + ['Chart paper','#efe9da','day-date disc, circular chart, comfort face, telegraph dial'], + ['Face ink','#14110e','numerals and printing on the paper faces'], + ['CRT face green','#b9d8c0','round-scope face tint'], + ['Sky slate','var(--slate-hi)','attitude-indicator sky'], + ['Earth brown','#5c4630','attitude-indicator ground'], + ['Instrument black','#17140f','bezels, plates and housings'], + ]], + ['Lamps, LEDs & jewels',[ + ['Lit amber','var(--gold-hi)','lit keys, sweep line, fixed indexes, blinkenlight lamps'], + ['Panel amber','var(--gold)','key gradients, bearing rings, seated pins, heading bug'], + ['Warning amber','var(--amber-warn)','warn cells, tripped collars, gear-transit lamps'], + ['Run green','var(--pass)','run lamps, gear greens, monitor bars, LIVE lamps'], + ['Fail terracotta','var(--fail)','muted keys, fault cells, TRIP button'], + ['LED green','var(--sevgrn)','seven-segment digits, battery cells'], + ['LED red','var(--sevred)','red seven-segment variant'], + ['Matrix red','#ff4a30','dot-matrix LEDs'], + ['Jewel red','var(--jewel-r)','pilot-lamp lens'], + ['Jewel amber','var(--jewel-a)','pilot-lamp lens, four-way corner lamps'], + ['Jewel green','var(--jewel-g)','pilot-lamp lens'], + ['Neon orange','#ff9a4c','nixie digits, dekatron glow, red screen family'], + ['Flip-disc yellow','#e3d44f','flip-dot faces'], + ['PDP magenta','#3a1420','blinkenlights panel skin'], + ]], + ['Screens & phosphors',[ + ['Phosphor green','var(--phos)','oscilloscope traces, green screen family'], + ['Phosphor dim','var(--phos-dim)','phosphor afterglow and dark screen grounds'], + ['VFD cyan','var(--vfd)','marquee, filter-bank arrows, vfd screen family'], + ['Graticule green','#3d5c46','CRT graticule rulings'], + ['LCD white','#f2f4f2','waveform-editor ink, white screen family'], + ['P4 blue-white','#cfe4ff','blue screen family'], + ]], + ['Needles & controls',[ + ['Needle red','#c23a28','comfort-meter needles, red hands'], + ['Control red','#e0523a','stop knobs, telegraph pointer, palm buttons, OFF marks'], + ['Pointer yellow','#e8cf4a','day-date read hand'], + ]], +]; +(function buildPalette(){ + const host=$('palette'); if(!host)return; + for(const [group,colors] of PALETTE){ + const h=document.createElement('div'); h.className='palhead'; h.textContent=group; + host.appendChild(h); + for(const [name,val,used] of colors){ + const tile=document.createElement('div'); tile.className='swatch'; + tile.innerHTML=`<div class="sw" style="background:${val}"></div>`+ + `<div class="swn">${name}</div><div class="swv">${used}</div>`; + host.appendChild(tile);}} +})(); + +/* ---- screen-color families: period-authentic alternates for the screen widgets ---- + green = P1 CRT phosphor (radar/scopes; hi reuses --phos, graticule keeps the kit's scope green) + amber = P3 phosphor / backlit amber (the gold family the kit already speaks) + red = neon orange-red, the nixie's color pulled out as a family + blue = P4 blue-white (early TV / precision scopes) + vfd = blue-green vacuum-fluorescent (the marquee's --vfd pulled out as a family) + white = monochrome LCD/CRT white + Exploration data, deliberately NOT in tokens.json yet — families get tokenized once picked, + the same way amber earned its tokens. Values keyed by the CSS custom property they drive. */ +/* SCREEN_FAMS lives in widgets.js (GW.SCREEN_FAMS) */ +function screenChips(no,el,fams,def){ + const cardEl=el.closest('.card'); if(!cardEl)return; + const row=document.createElement('div'); row.className='famchips'; + const g=document.createElement('span'); g.className='fgroup'; row.appendChild(g); + const lab=document.createElement('span'); lab.className='lab'; lab.textContent='screen'; g.appendChild(lab); + fams.forEach(f=>{const b=document.createElement('span'); + b.className='fc'+(f===def?' on':''); b.style.background=SCREEN_FAMS[f]['--scr-hi']; b.title=f; + b.addEventListener('click',()=>{ + for(const [k,v] of Object.entries(SCREEN_FAMS[f])) el.style.setProperty(k,v); + g.querySelectorAll('.fc').forEach(x=>x.classList.toggle('on',x===b)); + setRd(no,'screen '+f);}); + g.appendChild(b);}); + cardEl.querySelector('.opts').appendChild(row); +} +/* ---- per-card spec sheets ---- + input how you drive it, incl. the model: click widgets port to every target (Emacs click-regions + included); drag widgets have no Emacs drag equivalent and need a click/key idiom there; + display widgets take no input in real use (gallery interaction is demo only). + period present only when the component is clearly not timeless — use it to keep a console + period-consistent (swap a modern part for its historical counterpart or vice versa). */ +const INFO={ +'01':{input:'Click to flip. Click-only, ports everywhere incl. Emacs.', + solves:'One persistent on/off state, visible at a glance without reading text.', + use:'Common in every modern UI. Shines as a master control: power, mute, enable.', + limits:'Two states only. Ambiguous if the on-direction is not marked.', + origin:'Slide switches (transistor radios, Walkman hold) → the iPod hold switch with its orange reveal → the iOS toggle (2007). This card wears the hardware skin of that lineage.',difficulty:'Intuitive.', + prefer:'The state persists and gets glanced at more than changed. For period consoles use R03 or N01 instead.', + period:'The pill rendering is modern (iPod/iOS era); the slide-switch mechanism underneath is 1950s+.', + ref:'reference/slide-toggle.jpg'}, +'02':{input:'Click one key to engage it; the others release. Click-only.', + solves:'One-of-few mode selection where the active mode must glow.', + use:'Common on consoles. Shines for 2-4 modes switched often.', + limits:'Wide per option; past ~5 keys a selector reads better.', + origin:'Broadcast and studio consoles.',difficulty:'Intuitive.', + prefer:'Each mode deserves a lit, labeled, pressable identity.'}, +'03':{input:'Drag along the track. Drag-only — needs a click/key idiom for Emacs.', + solves:'Continuous level with position as the value readout.', + use:'Common in audio and settings. Shines when relative position matters.', + limits:'Precision bounded by track length; needs horizontal room.', + origin:'Mixing desks.',difficulty:'Intuitive.', + prefer:'The value is a level the eye should compare across channels.'}, +'04':{input:'Drag along the track. Drag-only — needs a click/key idiom for Emacs.', + solves:'Same as the horizontal fader in a column footprint.', + use:'Common. Shines in channel strips where many sit side by side.', + limits:'Needs vertical room; thumb occludes the scale mid-drag.', + origin:'Mixing desks.',difficulty:'Intuitive.', + prefer:'Levels stack in parallel columns (mixer, EQ bank).'}, +'05':{input:'Drag vertically to rotate. Drag-only — Emacs wants +/- keys or click-step.', + solves:'Continuous control in the smallest footprint of any analog input.', + use:'Common everywhere. Shines for gain, tune, adjust anywhere space is tight.', + limits:'Drag direction is a learned convention; fine setting needs a readout.', + origin:'Radio and audio gear.',difficulty:'Easy — the vertical-drag convention must be learned.', + prefer:'Space is tight and the exact value matters less than the gesture.'}, +'06':{input:'Click a segment. Click-only, ports everywhere.', + solves:'One-of-N selection with every option visible at once.', + use:'Common. Shines for 3-6 named, equally likely options.', + limits:'Options must fit the bar; long labels break it.', + origin:'Test equipment range switches, modern segmented controls.',difficulty:'Intuitive.', + prefer:'All options should stay readable without opening anything.'}, +'07':{input:'Click to toggle. Click-only.', + solves:'A lightweight boolean that reads as a tag, not a switch.', + use:'Common in filters and option sets. Shines in rows of independent flags.', + limits:'Low visual weight — poor for states that must shout.', + origin:'Modern web UI, filter chips.',difficulty:'Intuitive.', + prefer:'Many independent booleans sit together and density wins.'}, +'08':{input:'Two clicks: arm, then fire; disarms on timeout. Click-only.', + solves:'Destructive actions need friction sized to their consequence.', + use:'Specialty. Shines guarding resets, wipes, irreversible sends.', + limits:'Friction annoys if the action is actually routine.', + origin:'Missile and launch panels, industrial interlocks.',difficulty:'Easy.', + prefer:'A mis-click would cost real work — make intent two-step.'}, +'09':{input:'Click a lamp to toggle it. Click-only.', + solves:'A bank of independent booleans with lit state per item.', + use:'Common. Shines for channel enables, feature flags.', + limits:'Unlabeled lamps need a legend; color alone excludes colorblind users.', + origin:'Console channel strips.',difficulty:'Intuitive.', + prefer:'The set is small and each member needs its own light.'}, +'24':{input:'Click to step to the next position. Click-only.', + solves:'One-of-N selection with a physical pointer you can read across the room.', + use:'Common on period gear. Shines for mode/range knobs.', + limits:'Stepping is sequential — jumping to a far position takes several clicks.', + origin:'Rotary switches on radios and instruments.',difficulty:'Intuitive.', + prefer:'The selection should read like hardware, not a menu.'}, +'25':{input:'Click a numeral, a mark, or between marks for the units; focused, arrows step one unit. Click + keys.', + solves:'Reading a value against a long calibrated scale.', + use:'Specialty. Shines where the scale itself carries meaning.', + limits:'Scale literacy required; poor for quick setting.', + origin:'Slide rules, tuning scales.',difficulty:'Moderate.', + prefer:'The scale markings are the point, not just the value.', + period:'1930s-60s.'}, +'N01':{input:'Click to rock. Click-only.', + solves:'Mains-grade on/off with an unmistakable thrown state.', + use:'Common. Shines as the one switch that powers the whole unit.', + limits:'Visually heavy — wrong for minor options.', + origin:'Appliance and amplifier mains switches.',difficulty:'Intuitive.', + prefer:'The action is THE power decision for the device.'}, +'N02':{input:'Click a transport key; keys are mutually exclusive. Click-only.', + solves:'Media transport state with one lit verb at a time.', + use:'Common wherever playback exists. Shines with a moving mechanism to confirm state.', + limits:'The verb set is fixed by convention — do not repurpose.', + origin:'Tape decks.',difficulty:'Intuitive.', + prefer:'Anything plays, records, or rewinds.', + period:'1960s-80s tape era styling.'}, +'N03':{input:'Click a numbered preset. Click-only.', + solves:'Instant recall of a small set of saved destinations.', + use:'Common. Shines for stations, scenes, memory slots.', + limits:'Anonymous numbers — the user must remember the mapping.', + origin:'Car radio mechanical presets.',difficulty:'Intuitive.', + prefer:'A handful of favorites deserve one-press recall.', + period:'1950s-70s mechanical-preset styling.'}, +'N04':{input:'Drag either ring: inner and outer rotate independently. Drag-only, and the two targets need care in Emacs.', + solves:'Two related parameters in one knob footprint.', + use:'Specialty. Shines for coarse/fine or volume/tone pairs.', + limits:'Which ring is which must be learned; easy to grab the wrong one.', + origin:'Radio concentric controls.',difficulty:'Moderate.', + prefer:'Two parameters are so coupled they should live on one axis.', + period:'1940s-70s radio styling.'}, +'N05':{input:'Drag to spin; the LED ring shows position. Drag-only.', + solves:'Endless rotation where the value can also be set by the program.', + use:'Common on modern gear. Shines when presets move the value under the knob.', + limits:'No physical end stops — the ring is the only position cue.', + origin:'Digital synths and controllers.',difficulty:'Easy.', + prefer:'Software also writes the value the knob edits.', + period:'1980s onward.'}, +'N06':{input:'Click to turn the key through its positions. Click-only.', + solves:'Mode changes that should require deliberate, almost credentialed intent.', + use:'Specialty. Shines for maintenance/normal/locked modes.', + limits:'Slow by design; wrong for anything frequent.', + origin:'Machine and alarm key switches.',difficulty:'Easy.', + prefer:'Casual switching must be discouraged by the control itself.'}, +'N07':{input:'Drag along the track; a detent holds center. Drag-only.', + solves:'Balancing two sources with an honest neutral point.', + use:'Specialty. Shines for A/B blend, pan, crossfade.', + limits:'Only meaningful with exactly two poles.', + origin:'DJ mixers.',difficulty:'Intuitive.', + prefer:'The midpoint is a real state, not just halfway.'}, +'N08':{input:'Drag a wheel vertically to step its digit. Drag-only — Emacs wants per-digit +/-.', + solves:'Exact multi-digit numeric entry without a keyboard.', + use:'Specialty. Shines for frequencies, counts, codes set digit by digit.', + limits:'Slow for big changes; each digit is its own control.', + origin:'Avionics and test equipment.',difficulty:'Expert — digit-wise thinking.', + prefer:'Exactness beats speed and a keypad would be overkill.', + period:'1960s-80s.'}, +'N09':{input:'Click a switch to flip it. Click-only.', + solves:'A persistent configuration word set once and read rarely.', + use:'Specialty. Shines for boot-time or install-time options.', + limits:'Cryptic without its legend; not for live settings.', + origin:'Circuit-board DIP switches.',difficulty:'Expert — meaningless without the manual.', + prefer:'Config should be physical, deliberate, and survive resets.', + period:'1975-95 computing.'}, +'N10':{input:'Drag the inner jog for fine steps, the outer shuttle for velocity. Drag-only, two coupled gestures.', + solves:'Navigating a timeline at both frame and sweep speeds.', + use:'Specialty. Shines for scrubbing media or logs.', + limits:'Shuttle velocity mapping must be learned; hard to discover.', + origin:'Video edit decks.',difficulty:'Expert.', + prefer:'One control must cover both precision and distance.', + period:'1980s-90s edit suites.'}, +'R02':{input:'Drag to rotate the disc under the fixed hairline. Drag-only.', + solves:'Reading a continuous setting to a tenth of a division.', + use:'Specialty. Shines for calibration and lab-grade settings.', + limits:'Vernier reading is a skill; overkill for coarse values.', + origin:'Laboratory instruments.',difficulty:'Expert — vernier literacy.', + prefer:'The precision of the readback is the whole point.', + period:'1930s-60s lab styling.'}, +'R03':{input:'Click to throw the lever. Click-only.', + solves:'A binary with a big, satisfying, unmistakable throw.', + use:'Common on period gear. Shines where the thrown angle must be visible.', + limits:'Larger than a rocker for the same bit of state.', + origin:'Aviation and lab toggles.',difficulty:'Intuitive.', + prefer:'State must read from across the panel.'}, +'R04':{input:'Drag vertically to rotate. Drag-only.', + solves:'Same job as the rotary knob in a period skin.', + use:'Skin variant. Shines in 30s-50s compositions.', + limits:'As the rotary knob.', + origin:'Bakelite radio knobs.',difficulty:'Easy.', + prefer:'The console is dressed to that era.', + period:'1930s-50s.'}, +'R05':{input:'Drag each band vertically. Drag-only, one gesture per band.', + solves:'Shaping a curve by its bands, position = the curve itself.', + use:'Common as graphic EQ. Shines when the fader tops draw the response.', + limits:'Fixed bands; inter-band moves need many gestures.', + origin:'Filter banks, then graphic equalizers.',difficulty:'Easy.', + prefer:'The overall shape matters more than any single value.', + period:'1950s-60s studio filter styling.'}, +'R06':{input:'Click to snap to the next position. Click-only.', + solves:'One-of-few selection with an emphatic pointer.', + use:'Common on amps and lab gear. Shines for 2-4 coarse modes.', + limits:'Sequential stepping; the blade hides labels under it.', + origin:'Amplifier and instrument selectors.',difficulty:'Intuitive.', + prefer:'The selector should look like it belongs on a tube amp.', + period:'1940s-60s.'}, +'R12':{input:'Drag the handle along the slot. Drag-only.', + solves:'A signed level (boost/cut) around a marked zero.', + use:'Common on period hi-fi. Shines for tone and balance trims.', + limits:'Chrome furniture is wide for what it holds.', + origin:'Hi-fi tone controls.',difficulty:'Intuitive.', + prefer:'Zero is the default and deviation should be obvious.', + period:'1960s-70s hi-fi.'}, +'R14':{input:'Drag to rotate; the knurl ring turns with it. Drag-only.', + solves:'Slow, geared tuning across an engraved scale.', + use:'Skin variant for tuning. Shines in early-radio compositions.', + limits:'As the rotary knob; the engraved scale needs light to read.', + origin:'1920s receivers.',difficulty:'Easy.', + prefer:'The console leans into the earliest radio era.', + period:'1920s-30s.'}, +'R15':{input:'Drag the bandspread dial; click the band ring to switch bands. Mixed — the click half ports to Emacs, the drag half needs an idiom.', + solves:'One dial covering several ranges without losing resolution.', + use:'Specialty. Shines for shortwave-style band plus fine-tune.', + limits:'Band/bandspread interplay confuses newcomers.', + origin:'Multi-band receivers.',difficulty:'Expert.', + prefer:'The domain genuinely has bands (ranges, scopes, zoom levels).', + period:'1930s-50s.'}, +'R16':{input:'Click digits to buffer, OK to commit, C to clear. Click-only, fully Emacs-portable.', + solves:'Deliberate numeric entry with an explicit confirm step.', + use:'Common industrial. Shines for codes, channels, quantities.', + limits:'Slower than typing on a real keyboard when one exists.', + origin:'Industrial and security keypads.',difficulty:'Intuitive.', + prefer:'Entry must be explicit and cancellable, not live.'}, +'R18':{input:'Drag each lit column vertically; channels are independent. Drag-only.', + solves:'Two adjacent levels set by feel with lit numeric feedback.', + use:'Specialty. Shines for blend/mix pairs on processors.', + limits:'Compact but dense; the lit figure is the only precise cue.', + origin:'Compressor blend controls.',difficulty:'Easy.', + prefer:'Two coupled levels should sit shoulder to shoulder.', + period:'1970s-80s rack gear.'}, +'R19':{input:'Drag horizontally; the nearer flag follows, flags cannot cross. Drag-only.', + solves:'Marking a start/end region inside a visible signal.', + use:'Specialty. Shines for trims, loops, selections over a waveform.', + limits:'Flag semantics (S/E, no-cross) must be learned; small targets.', + origin:'Hardware samplers.',difficulty:'Expert.', + prefer:'The region IS the value and the data must stay visible.', + period:'1980s sampler styling.'}, +'R20':{input:'Drag each drum vertically to roll it. Drag-only.', + solves:'Stepped selection with a satisfying mechanical roll.', + use:'Specialty. Shines for paired coarse selections (EQ turnovers).', + limits:'Values hidden off-drum; sequential like all steppers.', + origin:'Channel-strip EQ rollers.',difficulty:'Easy.', + prefer:'A stepped pair should feel like machinery, not menus.', + period:'1960s console styling.'}, +'R21':{input:'Click a key; its LED takes the selection. Click-only.', + solves:'One-of-N program recall where the LED, not the key, carries state.', + use:'Common on rack gear. Shines for 4-8 named programs; maps directly onto a waybar workspace selector.', + limits:'One LED of state per key — no per-key value.', + origin:'Lexicon 224 program row.',difficulty:'Intuitive.', + prefer:'Selection state should read as a dot row even from an angle.', + period:'Late 1970s-80s digital rack.'}, +'R22':{input:'Click a zone to select it directly (A / AB / B). Click-only.', + solves:'Three-way routing with direct selection, no cycling.', + use:'Specialty. Shines for source/both/monitor selects.', + limits:'Three positions only; AB lighting both LEDs must be understood.', + origin:'Hi-fi speaker selectors.',difficulty:'Intuitive.', + prefer:'The middle state is a real combination, not a midpoint.'}, +'R23':{input:'Drag vertically to rotate. Drag-only.', + solves:'Same job as the rotary knob in a machined-metal skin.', + use:'Skin variant. Shines on 70s hi-fi faceplates.', + limits:'As the rotary knob.', + origin:'Spun-aluminum hi-fi knobs.',difficulty:'Easy.', + prefer:'The console is dressed silver-on-black.', + period:'1970s hi-fi.'}, +'R24':{input:'Click the stomp to latch; the jewel lights when engaged. Click-only.', + solves:'A rugged latch whose state glows at foot distance.', + use:'Common on pedals. Shines for engage/bypass.', + limits:'One bit; the jewel is the only state cue.', + origin:'Guitar stompboxes.',difficulty:'Intuitive.', + prefer:'The toggle should survive abuse and read from far away.'}, +'R27':{input:'Drag to step through the detents. Drag with detents — Emacs wants +/- steps.', + solves:'Stepped gain where each position is a calibrated, repeatable value.', + use:'Specialty. Shines for preamp gain in fixed dB steps.', + limits:'No values between detents by design.', + origin:'Neve-style console gain selectors.',difficulty:'Easy.', + prefer:'Repeatability beats continuous freedom.', + period:'1970s console styling.'}, +'R28':{input:'Click to step the disc to the next position, wrapping. Click-only.', + solves:'Heavy-duty source selection with an unmistakable handle.', + use:'Specialty. Shines for OFF/ON/COMBINE-style power routing.', + limits:'Sequential stepping; big footprint.', + origin:'Marine battery selectors.',difficulty:'Intuitive.', + prefer:'The selection routes something heavy and should look like it.'}, +'R29':{input:'Click the guard open, then click to throw; closing re-guards. Click-only, two-step.', + solves:'A switch that cannot be thrown by accident, period.', + use:'Specialty. Shines over anything irreversible.', + limits:'Two actions every time; reserve for genuine hazards.', + origin:'Aviation guarded switches.',difficulty:'Easy.', + prefer:'Like arm-to-fire but the guard state itself must be visible.'}, +'R32':{input:'Drag the dial to wind minutes on (turn to start); click the red knob to zero it (push to stop). Mixed — the stop is click-portable, the wind needs a key idiom in Emacs.', + solves:'Setting a duration and watching it run down, with OFF as a real position.', + use:'Common in kitchens and labs. Shines for timeboxes, pomodoros, sleep timers.', + limits:'One duration, coarse resolution; the demo runs a minute per second.', + origin:'Spring-driven interval timers.',difficulty:'Intuitive.', + prefer:'The remaining time should read as a dial position, not digits.'}, +'R33':{input:'Click an arrow or anywhere in its quadrant to step that direction. Click-only — the most Emacs-portable control in the kit, since it is literally arrow keys.', + solves:'Discrete navigation in four directions without a pointer position.', + use:'Common on remotes, cameras, avionics MFDs. Shines for menu and grid focus.', + limits:'Steps, not analog rate; diagonals need two presses; no held-repeat here.', + origin:'Remote-control and camera four-way pads.',difficulty:'Intuitive.', + prefer:'Focus moves through a grid or menu and each step is deliberate.'}, +'R34':{input:'Click a quadrant; the lever throws there and stays. Click-only, fully portable.', + solves:'One-of-four stateful selection where the selector itself shows the state.', + use:'Specialty. Shines for quadrant modes (channel pairs, axis pairs, routing).', + limits:'Exactly four states; diagonal labels need the printed legend.', + origin:'Test-panel quadrant switches with ball levers.',difficulty:'Intuitive.', + prefer:'The rocker steps a cursor; this holds a position. Reach for it when the four options are states, not directions.'}, +'R37':{input:'Click a row/column intersection to seat or pull a pin. Click-only, fully portable.', + solves:'Many-to-many routing where every active route stays simultaneously readable.', + use:'Specialty. Shines for source-to-destination mapping: audio routing, signal paths, rule wiring.', + limits:'Grid size is the ceiling; dense matrices need hover labels to stay legible.', + origin:'EMS VCS3 synthesizer pin matrix.',difficulty:'Easy — rows and columns must be read.', + prefer:'The patch bay shows one cable per route; the matrix shows the whole routing state at once.', + period:'Late 1960s-70s synth and routing gear.'}, +'R38':{input:'Press and hold; release anywhere drops to SAFE. Hold-based — pointer capture on web, a held key in Emacs; the only held-state control in the kit.', + solves:'Actions that must stop the moment the operator lets go.', + use:'Specialty. Shines for jog/feed, test firing, anything unsafe to walk away from.', + limits:'Fatigue is the design point — do not use it for long operations.', + origin:'Dead-man controls on trains, cranes, machine tools.',difficulty:'Intuitive.', + prefer:'Release-to-safe matters more than convenience.'}, +'R39':{input:'Click a hole; the wheel winds to the stop and returns, then the digit registers. Click-only, though the return animation is the point.', + solves:'Numeric entry with built-in pacing — larger digits genuinely take longer.', + use:'Specialty and nostalgic. Shines when entry should feel deliberate, or in period sets.', + limits:'Slow by design; ten digits only; no correction but a redial.', + origin:'Rotary telephone dials (Strowger pulse dialing).',difficulty:'Intuitive to those who have seen one.', + prefer:'The keypad enters numbers; the dial performs them.', + period:'1920s-70s telephony; hard-clashes with anything digital.'}, +'R40':{input:'Click a breaker to open or close it; TRIP pops one out under fault. A tripped breaker resets in two clicks: pull to off, then close. Click-only.', + solves:'Protective state the SYSTEM can change — no other control in the kit throws itself.', + use:'Common industrially. Shines for services/circuits that can fail independently: a tripped breaker is a crashed daemon.', + limits:'The tripped mid-position must be visually distinct or the story is lost.', + origin:'Aircraft and distribution breaker rows.',difficulty:'Easy — the two-step reset must be learned.', + prefer:'Faults should be visible at the switch, not in a separate alarm list.'}, +'R41':{input:'Press VERB or NOUN, type two digits, ENTR commits; V35 runs the lamp test, bad grammar lights OPR ERR. Click-only, fully portable.', + solves:'Reaching many functions from few keys via a verb-noun command grammar.', + use:'Specialty. Shines when the function space outgrows the panel space.', + limits:'The grammar must be memorized — the tradeoff is capability for discoverability.', + origin:'Apollo Guidance Computer DSKY.',difficulty:'Expert — by design.', + prefer:'Dozens of rare commands beat dozens of rare buttons.', + period:'1960s-70s spaceflight; the aesthetic survives in every command palette.'}, +'R42':{input:'Click to advance a step (or start from OFF); it then walks itself through the program. Click-only.', + solves:'A whole procedure shown as one rotating position — where you are, what came before, what is next.', + use:'Common on appliances and process gear. Shines for fixed multi-step sequences.', + limits:'The program is fixed at manufacture; skipping steps means clicking through them.', + origin:'Washing-machine and dishwasher cam timers.',difficulty:'Intuitive.', + prefer:'The sequence matters more than any single state — R30 shows a state, this shows a program.', + period:'1950s-80s appliance controls.'}, +'R48':{input:'Click to throw the blade open or closed. Click-only.', + solves:'A disconnect whose open state is physically visible — no lamp to trust, the gap IS the proof.', + use:'Specialty. Shines as a master isolate where "is it really off" matters.', + limits:'Semantically just a toggle; the value is entirely in the visible gap.', + origin:'Early power-station switchboards.',difficulty:'Intuitive.', + prefer:'A lamp says off; an air gap proves it. Use where proof beats economy of space.', + period:'1890s-1930s power switching; pure theater after that, and worth it.'}, +'R49':{input:'Drag each knob to its digit; the windows read the digits and the readout sums them. Drag with detents — Emacs wants per-digit +/-.', + solves:'Composing one precise value from independent per-decade digits.', + use:'Specialty. Shines for calibration values, resistances, counts where each digit is a deliberate choice.', + limits:'Range fixed by the decade count; slow for sweeping a value.', + origin:'General Radio decade resistance boxes.',difficulty:'Easy.', + prefer:'The thumbwheel sets a digit; this composes a NUMBER from digits with the arithmetic visible.', + period:'1930s-70s lab bench.'}, +'R50':{input:'Click one palm button, then the other within 0.5s; same hand twice or too slow faults. Click-only — the hardware holds both, the screen keeps the two-target-within-window grammar.', + solves:'Commands that must cost both hands — presence at two separated points before anything moves.', + use:'Specialty. Shines for genuinely destructive cycles where arm-to-fire is not enough.', + limits:'Deliberately annoying; the anti-tie-down rule must fault same-hand repeats or the guarantee is void.', + origin:'OSHA press and cutter controls.',difficulty:'Easy — the window must be learned.', + prefer:'Arm-to-fire proves intent; two-hand proves position. The strongest friction in the kit.'}, +'R51':{input:'Click a loop key: off → monitor → talk → off. Talk is exclusive; monitors are independent. Click-only, fully portable.', + solves:'Attending many channels at once while speaking on exactly one.', + use:'Specialty. Shines for channels, feeds, log streams — anything monitored in parallel.', + limits:'Flicker communicates activity, not content; past a dozen loops even controllers tiered them.', + origin:'NASA mission-control comm keysets.',difficulty:'Easy.', + prefer:'R21 selects one program; this holds many half-attended and one active.', + period:'1960s-90s console era; the pattern lives on in every notification mute matrix.'}, +'R52':{input:'Display plus the switch register: click a toggle to flip its bit and the activity pattern folds it in. The lamps take no input.', + solves:'Showing computation happening — activity, not just state.', + use:'Specialty. Shines for load, traffic, progress-with-texture; honest busy-ness.', + limits:'Patterns are read as rhythm and density, not decoded bit by bit.', + origin:'PDP-11/70 and IMSAI front panels.',difficulty:'Intuitive to watch, expert to read.', + prefer:'A spinner says busy; blinkenlights say HOW busy and with what rhythm.', + period:'1965-80 minicomputer era; the icon of computing at work.'}, +'R56':{input:'Drag the left half for temperature, the right for humidity. Drag-only, two surfaces.', + solves:'A categorical verdict from two values — the crossing point falls into a printed judgment.', + use:'Common on weather stations and comfort meters. Shines when the pair only matters as a combined condition: comfort, safe-operating region, duty envelope.', + limits:'The zone thresholds are opinions baked into the face; five zones is about the ceiling.', + origin:'Brass household comfort meters.',difficulty:'Intuitive — the verdict is literally written down.', + prefer:'N13 derives a number from the crossing, R55 reads two values separately; this one answers "is the combination OK" without asking you to do the math.'}, +'R54':{input:'Display in real use; drag vertically here to spin the tape. Drag-only for the demo.', + solves:'A long scale in a narrow window — the value sits at a fixed eye position with its neighborhood visible above and below.', + use:'Common in modern cockpits (airspeed, altitude) and rack meters. Shines when panel width is scarce and trend context matters.', + limits:'No at-a-glance needle angle; rate reads as scroll speed, which takes acclimation.', + origin:'Ki-57 remote tachometer; universal in glass cockpits since.',difficulty:'Easy.', + prefer:'The needle gauge gives angle-at-a-glance; the tape gives context-around-the-value in a tenth the width.'}, +'R55':{input:'Drag the left half for the left needle, right half for the right. Drag-only, two independent surfaces.', + solves:'Two related values in one instrument hole, each on its own mirrored scale.', + use:'Common on aircraft engine panels. Shines for pairs the eye checks together: fuel/oil, left/right, in/out.', + limits:'Both scales must share a range family or the mirroring misleads.', + origin:'Ki-57 fuel/oil pressure gauge; standard twin-engine instrument form.',difficulty:'Intuitive.', + prefer:'N13 crosses two needles to read their intersection; this houses two needles to read separately. Symmetry here means "both healthy looks symmetric".'}, +'R53':{input:'Display; click loads fresh paper. The real recorder takes no input.', + solves:'Cyclic time — the same hour lands at the same angle, so daily rhythm becomes shape.', + use:'Specialty. Shines for temperatures, loads, and rates with a daily or weekly cycle.', + limits:'One revolution of history; overlapping days need pen colors, like the hardware.', + origin:'Partlow and Honeywell circular chart recorders.',difficulty:'Easy.', + prefer:'The strip chart (N16) answers what just happened; this answers what this time of day usually looks like.', + period:'1900s-80s process recording.'}, +'10':{input:'Display; drag the gallery card to drive the demo value. Real use: none.', + solves:'A magnitude plus its rate of change, read peripherally.', + use:'Common. Shines where trend and zone matter more than digits.', + limits:'Coarse precision; needs face room.', + origin:'Moving-coil panel meters.',difficulty:'Intuitive.', + prefer:'The eye should catch movement, not read a number.'}, +'11':{input:'Display only; runs from the live demo signal.', + solves:'Two channels of level with peak behavior, at a glance.', + use:'Common in audio. Shines for stereo program level.', + limits:'Segment resolution; no history.', + origin:'LED meter bridges.',difficulty:'Intuitive.', + prefer:'Level must be watchable continuously without fatigue.'}, +'12':{input:'Display only.', + solves:'Signal strength in the smallest possible footprint.', + use:'Common. Shines in status bars and corners.', + limits:'Four levels of resolution, no more.', + origin:'Phone signal bars.',difficulty:'Intuitive.', + prefer:'One glance must answer roughly-how-strong.'}, +'13':{input:'Click a rung to set the demo level. Real use: display.', + solves:'A discrete level presented as steps rather than a continuum.', + use:'Common. Shines when the value naturally has rungs.', + limits:'Step count fixed by the ladder.', + origin:'Level ladders on meters and mixers.',difficulty:'Intuitive.', + prefer:'The scale is genuinely stepped (volume notches, severity).'}, +'14':{input:'Display; drag the gallery bar to drive the demo. Real use: none.', + solves:'How full, with zone warnings, in one horizontal read.', + use:'Common. Shines for capacity: fuel, disk, battery.', + limits:'One value; zones must be pre-agreed.', + origin:'Fuel gauges.',difficulty:'Intuitive.', + prefer:'The question is how much is left.'}, +'15':{input:'Display; click/drag drives the demo. Real use: none.', + solves:'Progress or proportion wrapped around a compact center.', + use:'Common in modern UI. Shines when the center holds the number.', + limits:'Angular reading is coarser than linear.', + origin:'Modern dashboard rings.',difficulty:'Intuitive.', + prefer:'A percentage needs to share space with its own label.', + period:'Modern (2010s UI) — pair with restraint on a period console.'}, +'16':{input:'Display only; live rolling data.', + solves:'The shape of recent history in a text-sized strip.', + use:'Common in dashboards. Shines inline next to a current value.', + limits:'No axes or absolute scale — trend only.', + origin:'Tufte sparklines.',difficulty:'Intuitive.', + prefer:'Direction-of-travel matters and space is text-sized.', + period:'Modern (2000s information design).'}, +'17':{input:'Display only; live signal.', + solves:'Raw signal texture over the last moments.', + use:'Specialty. Shines for audio/telemetry liveliness.', + limits:'Qualitative, not measured.', + origin:'Waveform strips on recorders.',difficulty:'Intuitive.', + prefer:'Presence and texture of a signal beat any single number.'}, +'N11':{input:'Display only; live trace.', + solves:'Seeing the waveform itself, not a summary of it.', + use:'Specialty. Shines for diagnosing shape, noise, clipping.', + limits:'Needs a real drawing surface everywhere it ports.', + origin:'Cathode-ray oscilloscopes.',difficulty:'Easy to watch, expert to interpret.', + prefer:'The shape of the signal is the information.'}, +'N12':{input:'Display only; live bands.', + solves:'Energy distribution across bands at a glance.', + use:'Common in audio. Shines for spectrum and per-core loads.', + limits:'Band resolution fixed; no phase/history.', + origin:'Spectrum analyzers and EQ displays.',difficulty:'Intuitive.', + prefer:'The question is where the energy sits, not how much total.'}, +'N13':{input:'Display only.', + solves:'Two related values on one face, their crossing point meaningful.', + use:'Specialty. Shines for forward/reflected power, in/out levels.', + limits:'Reading the iso-curves takes practice.', + origin:'Ham radio SWR meters.',difficulty:'Expert.', + prefer:'The ratio between two needles is the real value.'}, +'N14':{input:'Display only.', + solves:'A bounded scalar with universal instant recognition.', + use:'Common. Shines for temperature and anything temperature-like.', + limits:'Vertical footprint for one value.', + origin:'Mercury thermometers.',difficulty:'Intuitive.', + prefer:'The metaphor does the labeling for you.'}, +'N15':{input:'Display only.', + solves:'Industrial magnitude with legal-looking zone arcs.', + use:'Common industrial. Shines for pressure and load with red-line semantics.', + limits:'Face room; coarse read.', + origin:'Bourdon-tube pressure gauges (1849).',difficulty:'Intuitive.', + prefer:'The red zone must carry authority.'}, +'N16':{input:'Display only; live scrolling pen.', + solves:'A continuous value plotted against time, automatically.', + use:'Specialty. Shines for drift, cycles, events-over-hours.', + limits:'One channel per pen; horizontal footprint.', + origin:'Chart recorders.',difficulty:'Easy.', + prefer:'History matters as much as the current value.', + period:'Pre-digital logging (1900s-80s) styling.'}, +'N17':{input:'Display only.', + solves:'A signed relationship swinging around a marked zero.', + use:'Specialty. Shines for stereo correlation, balance, error.', + limits:'Meaning of + and - must be learned per domain.', + origin:'Broadcast correlation meters.',difficulty:'Moderate.', + prefer:'Zero is the healthy state and deviation is the signal.'}, +'N18':{input:'Display only.', + solves:'Charge state in discrete, honest cells.', + use:'Common. Shines for battery and quota displays.', + limits:'Cell resolution; green here is the LED green (sevgrn), not the phosphor green.', + origin:'Consumer battery gauges.',difficulty:'Intuitive.', + prefer:'Discrete cells match how users think about the resource.'}, +'R01':{input:'Display only; true VU ballistics on the live signal.', + solves:'Perceived loudness via standardized needle mass and dB law.', + use:'Common in audio. Shines as THE program-level meter.', + limits:'Slow by standard — misses fast peaks by design.', + origin:'The 1939 VU standard.',difficulty:'Easy.', + prefer:'Loudness as humans hear it beats instantaneous peaks.'}, +'R07':{input:'Display only.', + solves:'A dB read in a sealed porthole, panel-mount style.', + use:'Skin variant of the VU idea. Shines flush-mounted in rows.', + limits:'Small face, coarse scale.', + origin:'Panel meters on lab and broadcast gear.',difficulty:'Easy.', + prefer:'Several meters must sit in a disciplined row.', + period:'1950s-60s.'}, +'R08':{input:'Display only.', + solves:'A tuning/level window dressed in chrome for the living room.', + use:'Skin variant. Shines on consumer hi-fi faces.', + limits:'Ornamental furniture around a small read.', + origin:'Consumer hi-fi indicators.',difficulty:'Intuitive.', + prefer:'The console is consumer-facing, not lab-facing.', + period:'1960s-70s hi-fi.'}, +'R09':{input:'Display only.', + solves:'A flight-critical value with green/amber/red zones readable in the dark.', + use:'Specialty. Shines where out-of-zone must be instantly obvious.', + limits:'Zones must be authoritative or they train complacency.', + origin:'Aircraft engine and airspeed gauges.',difficulty:'Intuitive.', + prefer:'The zone, not the number, drives the response.'}, +'R13':{input:'Display only.', + solves:'A meter squeezed edgewise into a rack unit.', + use:'Common on rack gear. Shines where vertical space is the scarce resource.', + limits:'Compressed log scale takes a moment to read.', + origin:'Edgewise meters on processors.',difficulty:'Moderate.', + prefer:'Rack density forbids a round face.', + period:'1960s-80s rack.'}, +'R43':{input:'Drag: left/right banks, up/down pitches. The second 2D drag in the kit (after R26) — the hardest input model to port.', + solves:'Two-axis orientation read at a glance — no pair of scalar gauges can match it.', + use:'Specialty. Shines for anything with attitude, tilt, or balance across two axes.', + limits:'Needs interpretation training in the real world; here the drag teaches it.', + origin:'Cockpit artificial horizons.',difficulty:'Moderate.', + prefer:'The two values are physically one orientation, not two numbers.'}, +'R44':{input:'Drag to set the amber bug (commanded); the needle (actual) chases it with servo lag. Drag-only.', + solves:'Setpoint versus reality on one dial — the gap between bug and needle is the state.', + use:'Common in cockpits and process control. Shines for anything with a target and a lagging actual: thermostat, autopilot, motor speed.', + limits:'Wrap-around chase must take the short way or the story reads wrong.', + origin:'HSI heading bugs, synchro repeaters, motorized pointers.',difficulty:'Easy.', + prefer:'Showing only the actual hides the intent; showing both shows the system working.'}, +'R17':{input:'Display only; live trace. Screen chips switch the phosphor family.', + solves:'The round-tube scope look: waveform inside bezel and screws.', + use:'Skin variant of the oscilloscope. Shines in period lab compositions.', + limits:'Round face wastes corners; same porting need as any trace.', + origin:'Round-CRT lab scopes.',difficulty:'Easy to watch.', + prefer:'The era calls for a tube, not a rectangle.', + period:'1940s-60s.'}, +'18':{input:'Click cycles the demo state. Real use: display.', + solves:'One state, one lamp: ok, busy, fault at a glance.', + use:'Common everywhere. Shines beside the thing it describes.', + limits:'Color alone excludes colorblind users — pair with a label.', + origin:'Pilot lamps.',difficulty:'Intuitive.', + prefer:'A single state needs the smallest honest indicator.'}, +'19':{input:'Display only.', + solves:'A short status word with categorical color.', + use:'Common. Shines in lists and headers.', + limits:'Text-sized; not for continuous values.', + origin:'Tags and badges in modern UI.',difficulty:'Intuitive.', + prefer:'The state has a name and belongs inline.'}, +'20':{input:'Display only.', + solves:'Labeled values aligned for vertical scanning.', + use:'Common. Shines for 3-8 key/value facts.', + limits:'Static layout; long values break alignment.', + origin:'Instrument data plates.',difficulty:'Intuitive.', + prefer:'Several named numbers must be scannable in a column.'}, +'21':{input:'None — panel furniture.', + solves:'Naming a region so controls need shorter labels.', + use:'Common. Shines grouping related controls under one legend.', + limits:'Informational only.', + origin:'Engraved faceplate legends.',difficulty:'Intuitive.', + prefer:'A cluster needs a name more than each member does.'}, +'22':{input:'Display region; the overlay pair adds copy/clear.', + solves:'A place for streaming text output that is clearly not chrome.', + use:'Common in tools. Shines for logs and command output.', + limits:'Unstructured text; needs its own scroll discipline.', + origin:'Terminal wells in tool UIs.',difficulty:'Intuitive.', + prefer:'Output length is unbounded and text-shaped.'}, +'23':{input:'Display only; transient.', + solves:'A message that matters now but not forever.', + use:'Common. Shines for confirmations and background events.', + limits:'Easy to miss; sticky errors need a dismissal policy.', + origin:'Status lines and toasts.',difficulty:'Intuitive.', + prefer:'The message should not occupy permanent space.'}, +'26':{input:'Display only.', + solves:'A glowing digit with unmatched period warmth.', + use:'Specialty. Shines for counters and clocks meant to be loved.', + limits:'Neon is only ever orange — no color variants exist honestly; digits stack in depth.', + origin:'Nixie tubes.',difficulty:'Intuitive.', + prefer:'Warmth and era matter more than crispness.', + period:'1955-75 hard period: sits with keypads and telegraphs, clashes with VFD and LED seven-seg.'}, +'N20':{input:'Display only; flaps animate on change.', + solves:'A changed value that announces itself mechanically.', + use:'Specialty. Shines for arrivals-board style updates.', + limits:'Character set and flap speed are the charm and the constraint.', + origin:'Split-flap rail/airport boards.',difficulty:'Intuitive.', + prefer:'The moment of change deserves sound and motion.', + period:'1960s-90s boards.'}, +'N21':{input:'Display only.', + solves:'Digits readable at distance with segment honesty.', + use:'Common. Shines for counts, clocks, channel numbers.', + limits:'Digits and a few letters only.', + origin:'LED seven-segment displays.',difficulty:'Intuitive.', + prefer:'Numeric state should read like an appliance.', + period:'1975 onward.'}, +'N22':{input:'Display only; scrolls.', + solves:'More text than fits, one glowing window at a time.', + use:'Common on period appliances. Shines for track names, status crawl.', + limits:'Reading takes waiting; blue-green is the honest VFD color.', + origin:'Vacuum-fluorescent displays.',difficulty:'Intuitive.', + prefer:'A short crawl beats truncation.', + period:'1980s appliance/hi-fi.'}, +'R45':{input:'Display; the gallery makes each disc clickable. The real sign takes no input.', + solves:'Persistent visual state with zero holding power — the disc stays where it flipped.', + use:'Common on transit signs and industrial boards. Shines for slow-changing state that must survive power loss.', + limits:'Two colors, no glow (needs ambient light); flip rate limits animation.', + origin:'Flip-dot transit signs and magnetic indicators.',difficulty:'Intuitive.', + prefer:'The dot matrix glows and forgets; the flip-disc is dimmer but remembers.', + period:'1960s-90s transit era; still in service.'}, +'R46':{input:'Click = one pulse; the glow steps a cathode clockwise, wrapping with a carry blink. Click-only.', + solves:'A count where the counting itself is visible — position, motion, and carry.', + use:'Specialty. Shines for event counters and rate displays where each increment should be seen.', + limits:'One digit per tube; chains needed for big numbers, exactly like the hardware.', + origin:'Dekatron glow-transfer counter tubes.',difficulty:'Intuitive.', + prefer:'The nixie announces the digit; the Dekatron performs the increment.', + period:'1950s-60s counting gear; pre-IC logic made visible.'}, +'R47':{input:'Click the lever to cycle the gear; lamps pulse amber through transit. Click-only.', + solves:'Spatial status — each lamp is a physical thing in its physical place, plus an honest in-between state.', + use:'Specialty. Shines when components have positions (gear, doors, valves) and "moving" is a real state.', + limits:'The three-greens convention must be known; transit needs a timeout in real use.', + origin:'Aircraft landing-gear panels.',difficulty:'Intuitive.', + prefer:'The annunciator names conditions; this maps them onto the machine body.'}, +'N23':{input:'Click a cell to raise or cycle its alarm; ACK steadies the flashing master caution, RESET clears the board, TEST proves the bulbs. Click-only.', + solves:'Many named conditions, each with its own lit cell.', + use:'Specialty. Shines for fault boards where absence of light is health.', + limits:'Cell text must stay short; layout is fixed.', + origin:'Power-plant and aircraft annunciators.',difficulty:'Intuitive.', + prefer:'Every fault deserves a permanent, named home.'}, +'N24':{input:'Display only.', + solves:'A few states as colored jewels with period glow.', + use:'Common on period gear. Shines for power/signal/fault triples.', + limits:'Jewel green (jewel-g) is its own green — distinct from phosphor and VFD.', + origin:'Jewel pilot lamps.',difficulty:'Intuitive.', + prefer:'Indicator lights should be furniture, not pixels.', + period:'1930s-60s.'}, +'N25':{input:'Display only; counts.', + solves:'A resettable position count with mechanical drum digits.', + use:'Specialty. Shines for tape position, cycle counts.', + limits:'Relative number — meaningful only against a noted zero.', + origin:'Tape deck counters.',difficulty:'Easy.', + prefer:'A relative index beats an absolute timestamp.', + period:'1950s-80s decks.'}, +'N26':{input:'Display only; live time.', + solves:'Time of day, read culturally, instantly.', + use:'Common. Shines as console furniture that also works.', + limits:'Analog read is approximate by nature.', + origin:'Clocks.',difficulty:'Intuitive.', + prefer:'Approximate time at a glance beats digits.'}, +'N27':{input:'Display only.', + solves:'Where in the band the tuning currently sits.', + use:'Specialty. Shines paired with a tuning control.', + limits:'Furniture without its knob.', + origin:'Radio dial scales.',difficulty:'Intuitive.', + prefer:'Position-in-range should be visible independent of the control.', + period:'1930s-60s radio.'}, +'N28':{input:'Display only in the gallery; real bays patch.', + solves:'Which connections exist, shown as physical presence.', + use:'Specialty. Shines for routing state.', + limits:'Dense; jack labels carry all the meaning.', + origin:'Telephone and studio patch bays.',difficulty:'Moderate.', + prefer:'Connections are the state worth showing.'}, +'R10':{input:'Click steps the page. Screen chips switch the family. Otherwise display.', + solves:'Several small status pages in one fixed window.', + use:'Common on gear. Shines for SYS/NET/TIME-style rotations.', + limits:'One page visible; page order must be learned.', + origin:'Status LCDs on rack gear.',difficulty:'Intuitive.', + prefer:'A few short pages beat one crowded panel.', + period:'1980s-90s LCD era.'}, +'R11':{input:'Display only; the flag slides in on fault.', + solves:'A fault state that physically occludes the healthy read.', + use:'Specialty. Shines where ignoring a failure must be impossible.', + limits:'Binary; the barber pole means only one thing.', + origin:'Avionics warning flags.',difficulty:'Intuitive.', + prefer:'Failure must block the reading, not sit next to it.', + period:'1950s-70s avionics.'}, +'R25':{input:'Display only.', + solves:'Letters and digits in segments, still glowing, still period.', + use:'Specialty. Shines for short mode names (ZOOM, HALL).', + limits:'Fourteen segments approximate the alphabet, with charm.', + origin:'Alphanumeric LED displays.',difficulty:'Intuitive.', + prefer:'Words must glow like the rest of the rack.', + period:'1970s-80s.'}, +'R26':{input:'Drag the peak anywhere on the plot — the first 2D drag in the kit. Drag-only.', + solves:'A frequency response edited by grabbing the curve itself.', + use:'Common in DAWs. Shines for parametric EQ, any x/y parameter pair.', + limits:'Log-frequency mapping assumed knowledge; 2D drag is the hardest Emacs port in the kit.', + origin:'Mastering processor displays.',difficulty:'Expert.', + prefer:'Two coupled parameters live naturally on a plot.', + period:'Modern (digital-era display).'}, +'R35':{input:'Live readout initialized to today; click rolls midnight forward one day. Click-only.', + solves:'A compound value (weekday plus date) read at one index from coaxial discs.', + use:'Specialty. Shines wherever two coupled discrete values should share one pointer.', + limits:'The 31-slot date disc ignores short months — faithfully, since that is the real complication problem.', + origin:'Wristwatch day-date movements.',difficulty:'Intuitive.', + prefer:'Digits would work, but the mechanism itself is the display worth showing.', + period:'1950s-onward watchmaking; the exposed-movement look is the charm.'}, +'R36':{input:'Display; the gallery makes each dot clickable so the bitmap is paintable. The real part takes no input.', + solves:'A free-form bitmap in indicator form — glyphs, icons, patterns, tiny animations from one part.', + use:'Common in signs, elevators, synth modules. Shines when one small display must show anything.', + limits:'8x8 holds one glyph; multiplex flicker shows on camera; red-first technology.', + origin:'LED dot-matrix modules (HP displays, scrolling signs, the K4816 faceplate).',difficulty:'Intuitive.', + prefer:'Segments cover digits; reach for the matrix when the content is not characters.', + period:'1970s-90s LED-matrix era styling; the part is still made.'}, +'R30':{input:'Click steps the state, wrapping. Click-only.', + solves:'Current state shown by a pointer into labeled sectors.', + use:'Specialty. Shines for coarse machine states (RUN/STOP/TEST).', + limits:'Sector count fixed by the dial face.', + origin:'Ship engine telegraphs.',difficulty:'Intuitive.', + prefer:'State should read like a dial position, not a word.', + period:'1900s-50s marine styling.'}, +'R31':{input:'Click marks the current bearing. Screen chips switch the phosphor. Otherwise live display.', + solves:'Position around you, decaying like a real PPI.', + use:'Specialty. Shines for anything with bearing and range.', + limits:'The sweep metaphor implies periodic refresh.', + origin:'Radar plan-position indicators.',difficulty:'Easy to watch.', + prefer:'Angular position matters and the era wants a tube.', + period:'1940s-70s radar styling.'}, +}; +const INFO_FIELDS=[['input','input'],['solves','solves'],['use','use'],['limits','limits'], + ['origin','origin'],['difficulty','difficulty'],['prefer','prefer when'],['period','period']]; +const VLAMP_TITLES={off:'validation: not done',amber:'validation: in progress',green:'validation: done'}; +/* index tally: recounts every card lamp; setV calls it on every state change. + Clicking a row jumps to the next card in that state (cycles), so a count + that disagrees with a visual scan can be audited card by card. */ +const VJUMP={green:0,amber:0,off:0}; +function updateVTally(){ + const el=$('vtally'); if(!el)return; + const lamps=[...document.querySelectorAll('.vlamp')]; + const n={off:0,amber:0,green:0}; + lamps.forEach(l=>{n[l.dataset.v]=(n[l.dataset.v]||0)+1;}); + el.innerHTML= + `<div class="vrow" data-v="green"><span class="vdot" data-v="green"></span>done<span class="vn">${n.green}</span></div>`+ + `<div class="vrow" data-v="amber"><span class="vdot" data-v="amber"></span>in progress<span class="vn">${n.amber}</span></div>`+ + `<div class="vrow" data-v="off"><span class="vdot"></span>not done<span class="vn">${n.off}</span></div>`+ + `<div class="vrow vtot">total<span class="vn">${lamps.length}</span></div>`; + el.querySelectorAll('.vrow[data-v]').forEach(row=>row.addEventListener('click',()=>{ + VJUMP[row.dataset.v]=0; auditNext(row.dataset.v); + })); +} +/* audit stepper: a row click jumps to the first card in that state and docks a + floating pill; clicking the pill advances through the rest without scrolling + back to the index. The card list is re-read on every step, so lamp changes + mid-audit are picked up. Esc or the x dismisses. */ +const VAUDIT_LBL={green:'done',amber:'in progress',off:'not done'}; +function auditNext(v){ + const cards=[...document.querySelectorAll('.vlamp')].filter(l=>l.dataset.v===v) + .map(l=>l.closest('.card')); + if(!cards.length){auditStop();return;} + const i=VJUMP[v]%cards.length; VJUMP[v]++; + location.hash='';location.hash=cards[i].id; /* re-fire :target ring on repeat visits */ + let pill=$('vaudit'); + if(!pill){pill=document.createElement('div');pill.id='vaudit';document.body.appendChild(pill); + pill.addEventListener('click',e=>{ + if(e.target.classList.contains('vx')){auditStop();return;} + auditNext(pill.dataset.v);});} + pill.dataset.v=v; + pill.innerHTML=`<span class="vdot" data-v="${v==='off'?'':v}"></span>`+ + `${VAUDIT_LBL[v]} ${i+1}/${cards.length} · next ▸<span class="vx" title="stop auditing">✕</span>`; +} +function auditStop(){const p=$('vaudit');if(p)p.remove();} +addEventListener('keydown',e=>{if(e.key==='Escape')auditStop();}); +/* card(host, no, name, htmlOrBuild, note) — the declarative card record. + htmlOrBuild: a legacy stage-HTML string, or a builder function (stage, rd) => handle + that instantiates a GW.* widget into the stage; rd(txt) writes the card readout. */ +function card(host, no, name, html, note){ + const isBuild=typeof html==='function'; + const c=document.createElement('div'); c.className='card'; c.id='card-'+no; + c.innerHTML=`<div class="wname"><span class="no">${no}</span>${name}<span class="vlamp" data-v="off"></span></div>`+ + `<div class="stagew">${isBuild?'':html}</div><div class="wrd" id="rd-${no}">—</div>`+ + `<div class="opts"></div><div class="wnote">${note}</div>`; + const lamp=c.querySelector('.vlamp'), VKEY='gv-'+no, VSTATES=['off','amber','green']; + const setV=v=>{lamp.dataset.v=v;lamp.title=VLAMP_TITLES[v];updateVTally();}; + setV(localStorage.getItem(VKEY)||'off'); + lamp.addEventListener('click',e=>{e.stopPropagation(); + const v=VSTATES[(VSTATES.indexOf(lamp.dataset.v)+1)%3]; + setV(v);localStorage.setItem(VKEY,v);}); + const inf=(typeof INFO!=='undefined')&&INFO[no]; + if(inf){const d=document.createElement('details'); d.className='winfo'; + let rows=''; + for(const [k,lbl] of INFO_FIELDS) if(inf[k]) rows+=`<div class="ik">${lbl}</div><div class="iv">${inf[k]}</div>`; + if(inf.ref) rows+=`<div class="ik">reference</div><div class="iv"><a href="${inf.ref}" target="_blank">photo</a></div>`; + d.innerHTML=`<summary>spec sheet</summary><div class="igrid">${rows}</div>`; + c.appendChild(d);} + host.appendChild(c); + if(isBuild)c.gw=html(c.querySelector('.stagew'),txt=>setRd(no,txt)); + return c; +} +function setRd(no,txt){const e=$('rd-'+no);if(e)e.textContent=txt;} +/* drag helpers, seg7, buildBars live in widgets.js (GW) */ + +/* ============ CONTROLS ============ */ +const C=$('controls'); +card(C,'01','Slide toggle', + (st,rd)=>GW.slideToggle(st,{on:true,onChange:(v,t)=>rd(t)}), + '<b>on / off, the touchscreen way.</b> The one born-digital control in the kit — the smartphone pill toggle the live net/bt/audio panels use. Click to flip; readout shows the state. Physical cousins: the bat-handle (R03) and the rocker (N01).'); +card(C,'02','Console key', + (st,rd)=>GW.consoleKeys(st,{onChange:(i,t)=>rd(t)}), + '<b>physical push button.</b> Mutually exclusive — click one to engage. Gold = live, terracotta = muted.'); +card(C,'03','Horizontal fader', + (st,rd)=>GW.faderH(st,{value:68,onChange:(v,t)=>rd(t)}), + '<b>continuous 0-100.</b> Per-device volume, brightness. Drag the gold cap; readout tracks.'); +card(C,'04','Vertical fader', + (st,rd)=>{let ready=false; + const upd=()=>{if(ready)rd('A '+Math.round(a.get())+' · B '+Math.round(b.get()));}; + const a=GW.faderV(st,{value:60,onChange:upd}); + const b=GW.faderV(st,{value:35,onChange:upd}); + ready=true;upd();return {a,b};}, + '<b>channel-strip style.</b> A mixer column per device. Drag each fader up or down.'); +card(C,'05','Rotary knob', + (st,rd)=>GW.knob(st,{value:53,onChange:v=>rd('gain '+Math.round(v))}), + '<b>dial in a value.</b> Volume/gain the analog way. Drag up/down to turn; readout shows the level.'); +card(C,'06','Segmented selector', + (st,rd)=>GW.segmented(st,{onChange:(i,t)=>rd(t)}), + '<b>pick one of a few.</b> Timer type, layout mode. Click a segment; readout names the choice. The lit segment ships in amber, green, or red — the accent chips below switch it.'); +card(C,'07','Chip toggle', + (st,rd)=>GW.chipToggle(st,{on:true,onChange:(v,t)=>rd(t)}), + '<b>inline binary.</b> A soft toggle inside a line of text. Click to flip; gold when on.'); +card(C,'08','Arm-to-fire', + (st,rd)=>GW.armButton(st,{onChange:(v,t)=>rd(t)}), + '<b>two-stage confirm.</b> Destructive actions. First click arms (red), second fires. Readout shows the stage.'); +card(C,'09','Lamp row', + (st,rd)=>GW.lampRow(st,{onChange:(i,t)=>rd(t)}), + '<b>actionable list item.</b> Lamp + name + status. Click to cycle idle → connecting → connected.'); +card(C,'24','Rotary selector', + (st,rd)=>GW.rotarySelector(st,{onChange:(v,t)=>rd(t)}), + '<b>pick one of N by position.</b> Printed detents, the pointer names the value. Click to turn; readout shows it.'); +card(C,'25','Slide-rule dial', + (st,rd)=>GW.slideRule(st,{onChange:(v,t)=>rd(t)}), + '<b>value on a printed scale.</b> A lit pointer glides a warm-backlit strip. Printed numerals are the majors; the units between them carry minor ticks. Click any of them to jump, or focus and press ←/→ (↑/↓) to step a unit; readout shows it. Four faces — warm backlit, chrome, 80s black glass, marantz blue — on the chips below.'); +card(C,'N01','Rocker power switch', + (st,rd)=>GW.rocker(st,{on:true,onChange:(v,t)=>rd(t)}), + '<b>hard on / off, lit legend.</b> A master power paddle — the pressed half glows. Click to rock.'); +card(C,'N02','Transport cluster', + (st,rd)=>GW.transport(st,{mode:'play',animate:!reduced,onChange:(m,t)=>rd(t)}), + '<b>run / pause / capture.</b> One mode lit at a time, reels turn while it plays. Click a button.'); +card(C,'N03','Radio preset bank', + (st,rd)=>GW.presetBank(st,{onChange:(i,t)=>rd(t)}), + '<b>pick one, the rest release.</b> The car-radio preset row — mechanically exclusive. Click one.'); +card(C,'N04','Concentric dual knob', + (st,rd)=>GW.dualKnob(st,{outer:50,inner:50,onChange:(o,i)=>rd('bass '+Math.round(o)+' · treble '+Math.round(i))}), + '<b>two values on one spindle.</b> Bass / treble, coarse / fine. Drag the outer ring or inner cap — each is independent.'); +card(C,'N05','Rotary encoder + LED ring', + (st,rd)=>GW.encoder(st,{value:7,onChange:(v,t)=>rd(t)}), + '<b>endless dial, lit position.</b> A detentless encoder; the LED arc lights to the level and the value keeps accumulating. Drag to turn.'); +card(C,'N06','Keyed mode switch', + (st,rd)=>GW.keySwitch(st,{index:1,onChange:(i,t)=>rd(t)}), + '<b>guarded three-position mode.</b> Off · on · run. The key-bit points at the live position. Click to turn.'); +card(C,'N07','Center-detented crossfader', + (st,rd)=>GW.crossfader(st,{onChange:(v,t)=>rd(t)}), + '<b>throw to either side of zero.</b> A fader notched at center — balance, L/R mix. Drag; readout shows the signed value.'); +card(C,'N08','Thumbwheel', + (st,rd)=>GW.thumbwheel(st,{value:42,onChange:(v,t)=>rd(t)}), + '<b>ribbed edge-wheel, one value.</b> A knurled wheel with a windowed number. Drag up/down to roll.'); +card(C,'N09','DIP-switch bank', + (st,rd)=>GW.dipBank(st,{onChange:(w,t)=>rd(t)}), + '<b>a bank of hard flags.</b> Hardware slide switches — up is on. Click each to flip; readout is the binary word.'); +card(C,'N10','Jog / shuttle wheel', + (st,rd)=>GW.jogWheel(st,{onChange:(v,t)=>rd(t)}), + '<b>scrub fine, shuttle fast.</b> The edit-deck wheel. Drag to jog the inner wheel; readout shows the position.'); +card(C,'R02','Calibrated vernier dial', + (st,rd)=>GW.vernierDial(st,{value:42.0,onChange:(v,t)=>rd(t)}), + '<b>precision on a rotating disc.</b> The big lab dial — the calibrated disc turns under a fixed hairline. Drag up/down; readout reads to a tenth. After a 1950s lab-oscillator vernier.'); +card(C,'R03','Bat-handle toggle', + (st,rd)=>GW.batToggle(st,{on:true,onChange:(v,t)=>rd(t)}), + '<b>the period power switch.</b> Chrome bat lever on a hex bushing, snaps up ON / down OFF. Click to throw it. After a lab-instrument panel toggle.'); +card(C,'R04','Bakelite fluted knob', + (st,rd)=>GW.flutedKnob(st,{value:63,onChange:(v,t)=>rd(t)}), + '<b>the skirted console knob.</b> Scalloped bakelite skirt, glossy dome, amber index over a printed 0-10 scale. Drag up/down to turn. After a vintage console mixer knob.'); +card(C,'R05','Filter slider bank', + (st,rd)=>GW.filterBank(st,{onChange:(v,t)=>rd(t)}), + '<b>a wall of band faders.</b> Twelve bands on a screwed faceplate, dB rails both sides. Drag any cap; readout names band and level. Skins split three independent axes — panel (silver hi-fi after the Pioneer SG-9500 / studio black after the Technics SH-8065), cap shape (tall block fader after the Zaxcom Oasis / short ribbed / chrome T), and cap color (black white-index / red / green / blue / amber stripes / chrome / cream). The chips below mix them freely.'); +card(C,'R06','Chicken-head selector', + (st,rd)=>GW.chickenHead(st,{index:2,onChange:(i,t)=>rd(t)}), + '<b>the pointer-lever switch.</b> The tapered bakelite lever IS the indicator — it aims at the engraved position. Click to step through. After a modulator mode switch.'); +card(C,'R12','Chrome slot fader', + (st,rd)=>GW.slotFader(st,{value:-4,onChange:(v,t)=>rd(t)}), + '<b>the console gain slide.</b> A chrome T-handle rides a recessed slot, the dB scale engraved on the plate — gain above zero, cut below. Drag; readout shows signed dB. After a vintage channel-strip output fader.'); +card(C,'R14','Spade-pointer tuning knob', + (st,rd)=>GW.spadeKnob(st,{value:8.3,onChange:(v,t)=>rd(t)}), + '<b>the pointer rides the knob.</b> A knurled bakelite knob carries a bright spade pointer over a scale engraved into the panel itself. Drag up/down to tune. After a 1920s radio receiver dial.'); +card(C,'R15','Multi-band dial', + (st,rd)=>GW.multiBandDial(st,{onChange:(v,t)=>rd(t)}), + '<b>four scales, one needle.</b> Nested band arcs on a cream face; the active ring reads bold. Drag the left dial to tune; click the bandspread dial to switch bands. After a shortwave receiver dial pair.'); +card(C,'R16','Entry keypad', + (st,rd)=>GW.entryKeypad(st,{onChange:(v,t)=>rd(t)}), + '<b>a composed entry instrument.</b> Worn keycaps feed the amber display; the lamp watches the buffer; the amber keys confirm and clear. Click the keys. After an industrial keypad prop.'); +card(C,'R18','Thumb-slide attenuator pair', + (st,rd)=>GW.thumbSlide(st,{onChange:(v,t)=>rd(t)}), + '<b>a lit column of numbers, a thumb tab.</b> The scale glows inside the rail; the cream tab rides beside it and the nearest figure lights. Drag either channel. After a compressor blend/mix pair.'); +card(C,'R19','Waveform region editor', + (st,rd)=>GW.waveRegion(st,{start:22,end:76,onChange:(v,t)=>rd(t)}), + '<b>pick a region on the wave.</b> The monochrome LCD shows the sample; drag near either flag to move START or END, and the selection draws bright. After a sampler edit screen.'); +card(C,'R20','Drum roller selector', + (st,rd)=>GW.drumRoller(st,{onChange:(v,t)=>rd(t)}), + '<b>roll to the number.</b> The numbered drum shows through a tall window and the center value sits on an inverted chip. Drag either drum. After an equalizer tone selector.'); +card(C,'R21','LED program row', + (st,rd)=>GW.ledRow(st,{index:5,onChange:(v,t)=>rd(t)}), + '<b>identical keys, one lit dot.</b> The LED above the button carries the selection, not the key itself — the classic program-select row. Click a key; readout names the program. After a digital reverb’s program bank.'); +card(C,'R22','Three-position slide', + (st,rd)=>GW.pillSlide(st,{index:1,onChange:(v,t)=>rd(t)}), + '<b>a detented pill, three stops.</b> The chrome pill parks at A, AB, or B, and the LED pair below tells the truth — both light on AB. Click a position. After a drum machine variation switch.'); +card(C,'R23','Spun-aluminum knob', + (st,rd)=>GW.spunKnob(st,{value:62,onChange:(v,t)=>rd(t)}), + '<b>bright metal, machined rings.</b> A spun face with concentric tooling marks in a knurled grip ring, red index over dot ticks. Drag up/down. After a boutique pedal knob, confirmed by an avionics cluster.'); +card(C,'R24','Stomp switch + jewel', + (st,rd)=>GW.stompSwitch(st,{on:false,onChange:(v,t)=>rd(t)}), + '<b>the engage pair.</b> A chrome dome stomp and the faceted jewel that reports it — press to engage, the jewel lights; press again to bypass. Click it. After a tremolo pedal footswitch.'); +card(C,'R27','Winged gain selector', + (st,rd)=>GW.wingSelector(st,{index:7,onChange:(v,t)=>rd(t)}), + '<b>the red T-bar, stepped.</b> A bar-grip handle over a dot ring, snapping between labeled gain detents. Drag up/down to step it. After a classic mic-preamp gain switch.'); +card(C,'R28','Rotary disc switch', + (st,rd)=>GW.discSwitch(st,{index:1,onChange:(v,t)=>rd(t)}), + '<b>the master power disc.</b> The whole red disc turns, grip bar molded in — a heavy three-position selector for OFF, ON, and COMBINE. Click to step. After a marine battery switch.'); +card(C,'R29','Guarded toggle', + (st,rd)=>GW.guardedToggle(st,{on:true,onChange:(v,t)=>rd(t)}), + '<b>friction sized to consequence.</b> Guard posts flank the lever and a red collar marks it critical — the avionics way to prevent an accidental throw. Click to throw it deliberately. After a flight-deck switch panel.'); +card(C,'R32','Mechanical timer dial', + (st,rd)=>GW.timerDial(st,{minutes:0,onChange:(v,t)=>rd(t)}), + '<b>turn to start, push to stop.</b> Drag the knurled dial to wind minutes onto the fixed red index; it winds back down to OFF on its own (demo runs a minute per second). Click the red knob to stop it dead. After a mechanical interval timer.'); +card(C,'R33','Four-way rocker', + (st,rd)=>GW.rockerPad(st,{onChange:(v,t)=>rd(t)}), + '<b>discrete direction, one press at a time.</b> Click an arrow (the whole quadrant is the target) to step that way; the readout tracks the cursor position it drives. After a remote-control navigation pad.'); +card(C,'R34','Four-way toggle selector', + (st,rd)=>GW.fourWayToggle(st,{position:'C',onChange:(v,t)=>rd(t)}), + '<b>a lever that lives in one of four states.</b> Click a quadrant and the ball lever throws to A, B, C or D; the matching corner lamp takes the light. The rocker steps — this one selects. After a test-panel quadrant switch.'); +card(C,'R37','Pin routing matrix', + (st,rd)=>GW.pinMatrix(st,{onChange:(v,t)=>rd(t)}), + '<b>many-to-many routing you can read.</b> Sources are rows, destinations are columns; click an intersection to seat a pin and the route exists. Every simultaneous route stays visible. After the EMS VCS3 pin matrix.'); +card(C,'R38','Dead-man button', + (st,rd)=>GW.deadMan(st,{onChange:(v,t)=>rd(t)}), + '<b>active only while held.</b> Press and hold — the ring arms, the lamp runs, the dwell clock counts. Let go anywhere and it drops to SAFE. The kit has toggles and momentaries; this is the only control whose state IS your grip.'); +card(C,'R39','Rotary telephone dial', + (st,rd)=>GW.telephoneDial(st,{onChange:(v,t)=>rd(t)}), + '<b>numeric entry embodied as time.</b> Click a hole: the finger wheel winds to the stop and spins back, and the digit lands in the number. Bigger digits take longer, exactly like the pulses did. After a Western Electric dial.'); +card(C,'R40','Circuit breaker panel', + (st,rd)=>GW.breakerPanel(st,{onChange:(v,t)=>rd(t)}), + '<b>the first control the system can throw.</b> Click a breaker to close or open it; hit TRIP and a fault pops one out to the tripped mid-position, amber collar showing. A tripped breaker must be pulled to reset before it closes again. After an aircraft breaker row.'); +card(C,'R41','DSKY verb-noun panel', + (st,rd)=>GW.dsky(st,{onChange:(v,t)=>rd(t)}), + '<b>a command grammar, not a button per command.</b> VERB is the action, NOUN is the operand: press VERB 3 5 ENTR for the lamp test. Wrong grammar lights OPR ERR. Two keys plus digits reach every function — the Apollo answer to a panel with too few buttons.'); +card(C,'R42','Cam-timer program drum', + (st,rd)=>GW.camTimer(st,{position:0,onChange:(v,t)=>rd(t)}), + '<b>a procedure as a rotating position.</b> The ring is the whole program — fill, wash, rinse, spin — and the pointer is where you are in it. Click to advance (or start from OFF); it then walks itself through the steps. After a washing-machine cam timer.'); +card(C,'R48','Knife switch', + (st,rd)=>GW.knifeSwitch(st,{closed:true,onChange:(v,t)=>rd(t)}), + '<b>the visible disconnect.</b> Copper blades either sit in their jaws or hang open in the air — you can SEE the circuit is broken, which is the entire safety argument. Click the handle to throw it. After early power-station switchboards.'); +card(C,'R49','Decade switch box', + (st,rd)=>GW.decadeBox(st,{digits:[3,5,0,0],onChange:(v,t)=>rd(t)}), + '<b>a number composed a digit at a time.</b> Four skirted knobs, one per decade — drag each to its digit and the total just IS their sum. The thumbwheel does one digit; this is how a lab built a precise value. After a General Radio decade resistance box.'); +card(C,'R50','Two-hand safety control', + (st,rd)=>GW.twoHandSafety(st,{onChange:(v,t)=>rd(t)}), + '<b>both hands, deliberately.</b> Click one palm button to arm the window, the other within half a second to cycle the press. Too slow — TIE-DOWN fault, start over. The hardware keeps hands out of the die; the grammar survives on screen. After OSHA press controls.'); +card(C,'R51','Voice-loop keyset', + (st,rd)=>GW.voiceLoop(st,{onChange:(v,t)=>rd(t)}), + '<b>attention management, not selection.</b> Each loop is independent: click to monitor (green bar, flickering with activity), again to talk (amber, exclusive — one voice out), again to drop. Flight controllers ran a dozen of these at once. After a mission-control comm keyset.'); + +/* ============ METERS & GAUGES ============ */ +const M=$('meters'); +card(M,'10','Needle gauge', + (st,rd)=>GW.needleGauge(st,{value:50,onChange:(v,t)=>rd(t)}), + '<b>analog dial.</b> Throughput, battery, volume. Drag up/down to sweep the needle; readout tracks. <b>Smooth sweep wants a Cairo/GTK area.</b>'); +card(M,'11','Stereo VU (LED bar)', + (st,rd)=>GW.vuPair(st,{onChange:(v,t)=>rd(t)}), + '<b>live signal level.</b> The sound panel meter row, peak-hold outline. Runs a live signal; readout shows L/R.'); +card(M,'12','Mini signal (4-bar)', + (st,rd)=>GW.miniSig(st,{onChange:(v,t)=>rd(t)}), + '<b>compact activity.</b> Per-row "is this device playing". Live signal; readout shows activity %.'); +card(M,'13','Signal ladder', + (st,rd)=>GW.signalLadder(st,{value:3,onChange:(v,t)=>rd(t)}), + '<b>discrete strength.</b> Wifi bars, bt RSSI — a stepped 0-4. Click to cycle strength; readout shows bars.'); +card(M,'14','Linear fuel bar', + (st,rd)=>{const a=GW.fuelBar(st,{value:72,onChange:(v,t)=>rd(t)}); + const b=GW.fuelBar(st,{value:40,onChange:(v,t)=>rd(t)}); + a.set(72);return {a,b};}, + '<b>a single 0-100.</b> Battery, disk, download. Drag either bar; warn tint under threshold; readout tracks.'); +card(M,'15','Radial ring', + (st,rd)=>GW.radialRing(st,{value:68,onChange:(v,t)=>rd(t)}), + '<b>percentage as a donut.</b> CPU, battery. Drag up/down to set; conic-gradient tracks the readout.'); +card(M,'16','Sparkline', + (st,rd)=>GW.sparkline(st,{onChange:(v,t)=>rd(t)}), + '<b>recent history.</b> Throughput/CPU over the last minute. Live trace; readout shows the current value.'); +card(M,'17','Waveform strip', + (st,rd)=>GW.waveStrip(st,{onChange:(v,t)=>rd(t)}), + '<b>audio waveform / scope.</b> A richer signal view. Live trace; readout shows amplitude. <b>Needs a drawing surface.</b>'); +card(M,'N11','Oscilloscope', + (st,rd)=>GW.scope(st,{onChange:(v,t)=>rd(t)}), + '<b>a live phosphor trace.</b> A waveform swept over a graticule. Live; readout shows Vpp. <b>Needs a Cairo/GTK area for a smooth trace.</b>'); +card(M,'N12','Spectrum / EQ bars', + (st,rd)=>GW.eqBars(st,{onChange:(v,t)=>rd(t)}), + '<b>level across bands.</b> A live multi-band bar graph — spectrum, per-core CPU. Live; readout shows the peak band.'); +card(M,'N13','Crossed-needle meter', + (st,rd)=>GW.crossNeedle(st,{value:55,onChange:(v,t)=>rd(t)}), + '<b>two readings, one face.</b> Forward + reflected needles cross. Drag up/down to drive power; readout shows both. <b>Round build wants a drawing surface.</b>'); +card(M,'N14','Thermometer column', + (st,rd)=>GW.thermometer(st,{value:58,onChange:(v,t)=>rd(t)}), + '<b>a rising column.</b> The classic mercury tube — temperature, a climbing load. Drag up/down; readout shows the value.'); +card(M,'N15','Bourdon pressure gauge', + (st,rd)=>GW.bourdon(st,{value:45,onChange:(v,t)=>rd(t)}), + '<b>a round dial with a red zone.</b> Needle over a printed arc, redline in terracotta. Drag to set PSI; readout tracks. <b>Round sweep is a drawing-area job.</b>'); +card(M,'N16','Strip-chart recorder', + (st,rd)=>GW.stripChart(st,{onChange:(v,t)=>rd(t)}), + '<b>a scrolling pen trace.</b> History scrolls left, the pen writes at the right edge. Live; readout shows the current value. <b>Real trace wants a drawing surface.</b>'); +card(M,'N17','Correlation meter (±)', + (st,rd)=>GW.corrMeter(st,{value:58,onChange:(v,t)=>rd(t)}), + '<b>deviation from center.</b> The needle rests at 0 and swings ±. Drag left/right; readout shows the signed value.'); +card(M,'N18','Battery-cell gauge', + (st,rd)=>GW.battCells(st,{value:62,onChange:(v,t)=>rd(t)}), + '<b>charge as discrete cells.</b> Filled cells count up, the last warn in terracotta. Drag left/right to set; readout shows %.'); +card(M,'R01','Moving-coil VU meter', + (st,rd)=>GW.mcVu(st,{onChange:(v,t)=>rd(t)}), + '<b>the classic cream-faced VU.</b> Printed dB arc, red zone past 0, ballistic needle. Runs the live signal; readout shows VU. After a vintage console meter pair.'); +card(M,'R07','Round panel meter', + (st,rd)=>GW.roundMeter(st,{value:50,onChange:(v,t)=>rd(t)}), + '<b>the porthole lab meter.</b> Cream dial in a round black bezel, printed dB arc, black needle. Drag up/down to drive it; readout shows dB. After a modulator front meter.'); +card(M,'R08','Chrome MIN/MAX indicator', + (st,rd)=>GW.chromeMinMax(st,{value:50,onChange:(v,t)=>rd(t)}), + '<b>the hi-fi trim window.</b> Polished chrome ring, brushed dome, a dark pointer sweeping MIN to MAX over the metal. Drag up/down; readout shows the position. After a hi-fi amplifier level window.'); +card(M,'R09','Black-face aviation gauge', + (st,rd)=>GW.aviationGauge(st,{value:52,onChange:(v,t)=>rd(t)}), + '<b>amber on black, zone-marked.</b> The cockpit round: amber ticks and numerals on a black face, painted caution and limit sectors, amber needle. Drag up/down; readout tracks. After a flight-instrument cluster.'); +card(M,'R13','Edgewise strip meter', + (st,rd)=>GW.edgeMeter(st,{value:.62,onChange:(v,t)=>rd(t)}), + '<b>a needle on a vertical scale.</b> The parchment strip reads attenuation down a compressed log scale; the bar rides its edge. Drag up/down; readout shows dB. After a channel-strip gain-reduction meter.'); +card(M,'R43','Attitude indicator', + (st,rd)=>GW.attitudeIndicator(st,{onChange:(v,t)=>rd(t)}), + '<b>orientation, not a number.</b> The horizon rolls and shifts behind the fixed amber airplane — two axes in one read. Drag it: left/right banks, up/down pitches. The first two-axis instrument in the kit. After a cockpit artificial horizon.'); +card(M,'R44','Heading bug + servo needle', + (st,rd)=>GW.headingBug(st,{value:90,onChange:(v,t)=>rd(t)}), + '<b>commanded versus actual on one dial.</b> Drag to park the amber bug where you want to go; the needle chases it with honest servo lag. The gap between them IS the information. After an HSI heading bug and synchro repeaters.'); +card(M,'R54','Vertical tape instrument', + (st,rd)=>GW.verticalTape(st,{value:24,onChange:(v,t)=>rd(t)}), + '<b>the scale moves, the index does not.</b> A scrolling tape behind a fixed amber index — your value is always at eye level and the neighborhood above and below stays visible. Drag to spin the tape. After the Ki-57 remote tachometer; modern cockpits tape everything.'); +card(M,'R55','Twin-needle gauge', + (st,rd)=>GW.twinNeedle(st,{fuel:2.4,oil:3.1,onChange:(v,t)=>rd(t)}), + '<b>two values, one housing, mirrored scales.</b> Fuel presses up the left arc, oil up the right, each with its own needle from the shared hub. Drag either half to set that side. Not the crossed-needle (N13) — these are read separately, not at their intersection. After the Ki-57 fuel/oil pressure gauge.'); +card(M,'R56','Comfort-zone crossed needles', + (st,rd)=>GW.comfortMeter(st,{temp:72,humidity:45,onChange:(v,t)=>rd(t)}), + '<b>two values, one verdict.</b> Temperature and humidity needles cross over a face printed with judgments — the crossing point lands in TOO WARM, TOO DRY, or JUST RIGHT. N13 derives a number from the crossing; this one derives an opinion. Drag each half. After a brass weather-station comfort meter.'); +card(M,'R53','Circular chart recorder', + (st,rd)=>GW.chartRecorder(st,{onChange:(v,t)=>rd(t)}), + '<b>cyclic time on round paper.</b> The pen sweeps a full day per revolution, so "same time yesterday" is the same angle and daily rhythm draws itself as a flower. Click for fresh paper. The strip chart shows a window; this shows the cycle. After a Partlow round-chart recorder.'); +card(M,'R17','Round CRT scope', + (st,rd)=>GW.roundCrt(st,{onChange:(v,t)=>rd(t)}), + '<b>the round-tube scope.</b> Pale phosphor face in a screwed porthole bezel, dark graticule, a bright live trace. Runs live; readout shows Vpp. After a 1950s bench oscilloscope.'); + +/* ============ INDICATORS & READOUTS ============ */ +const I=$('indicators'); +card(I,'18','Status lamp', + (st,rd)=>GW.statusLamps(st,{onChange:(v,t)=>rd(t)}), + '<b>one-glance health.</b> Green ok · gold engaged · red fail · dim off · pulsing busy. Click any lamp to cycle its state.'); +card(I,'19','Badge / tag', + (st,rd)=>GW.badges(st,{onChange:(v,t)=>rd(t)}), + '<b>a labelled flag.</b> MUTED, AIRPLANE, a band tag. Click a badge to cycle its variant.'); +card(I,'20','Tabular readout', + (st,rd)=>GW.tabularReadout(st,{onChange:(v,t)=>rd(t)}), + '<b>a precise number.</b> Clock, countdown, volume %. Runs a live countdown; click to pause/resume.'); +card(I,'21','Engraved label', + (st,rd)=>GW.engravedLabel(st,{onChange:(v,t)=>rd(t)}), + '<b>section divider.</b> The hairline-flanked caps label with a count. Click to bump the count.'); +card(I,'22','Output well', + (st,rd)=>GW.outputWell(st,{onChange:(v,t)=>rd(t)}), + '<b>streaming step log.</b> Lamp-per-step with evidence. Click to stream the next step.'); +card(I,'23','Toast / status line', + (st,rd)=>GW.toast(st,{onChange:(v,t)=>rd(t)}), + '<b>transient confirmation.</b> The one-line result after an action. Click to fire the next toast.'); +card(I,'26','Nixie tube', + (st,rd)=>GW.nixie(st,{onChange:(v,t)=>rd(t)}), + '<b>a single warm-glowing numeral.</b> One lit digit per tube, leading zeros dark. Click to increment the count.'); +card(I,'N20','Split-flap display', + (st,rd)=>GW.splitFlap(st,{animate:!reduced,onChange:(v,t)=>rd(t)}), + '<b>flips to the new value.</b> Each card drops to the next glyph with the mechanical clack. Auto-flips; click to flip now.'); +card(I,'N21','Seven-segment display', + (st,rd)=>GW.sevenSeg(st,{onChange:(v,t)=>rd(t)}), + '<b>a lit-segment number.</b> Green segments, distinct from the nixie glow. Live countdown; click to add a minute.'); +card(I,'N22','VFD marquee', + (st,rd)=>GW.vfdMarquee(st,{onChange:(v,t)=>rd(t)}), + '<b>scrolling status line.</b> Teal glow, dot-matrix mesh, text tracks across. Scrolls live; click to change the message.'); +card(I,'N23','Annunciator panel', + (st,rd)=>GW.annunciator(st,{onChange:(v,t)=>rd(t)}), + '<b>a grid of named alarms with the alarm lifecycle.</b> Cells light amber to warn, red to fault; any active alarm flashes MSTR CAUTION until ACK steadies it, a new alarm re-flashes it, RESET clears the board, TEST proves the bulbs. Click cells to raise alarms.'); +card(I,'N24','Jewel pilot lamps', + (st,rd)=>GW.jewels(st,{onChange:(v,t)=>rd(t)}), + '<b>a faceted glass indicator.</b> The chunky bezel pilot lamp with a real bloom. Click a jewel to cycle red · amber · green · dark.'); +card(I,'N25','Tape counter', + (st,rd)=>GW.tapeCounter(st,{onChange:(v,t)=>rd(t)}), + '<b>rolling odometer digits.</b> Numbered wheels roll to the next figure. Rolls live; readout shows the total.'); +card(I,'N26','Analog clock', + (st,rd)=>GW.analogClock(st,{onChange:(v,t)=>rd(t)}), + '<b>time on a real face.</b> Engraved ticks, three hands, live seconds sweep. Runs live; readout shows the time.'); +card(I,'N27','Frequency-dial scale', + (st,rd)=>GW.freqScale(st,{onChange:(v,t)=>rd(t)}), + '<b>a printed log scale.</b> Marks crowd low, spread high — a real logarithmic axis. Drag the pointer; readout shows the frequency.'); +card(I,'N28','Patch-bay jack field', + (st,rd)=>GW.patchBay(st,{onChange:(v,t)=>rd(t)}), + '<b>routing shown as patches.</b> A grid of jacks with cables between connected pairs. Click one jack then another to add/remove a cable.'); +card(I,'R10','Data matrix readout', + (st,rd)=>GW.dataMatrix(st,{onChange:(v,t)=>rd(t)}), + '<b>a page of amber fields.</b> The console data block — several labeled values on one lit matrix. Click to cycle pages; readout names the page. After a cockpit data readout.'); +card(I,'R11','Warning flag window', + (st,rd)=>GW.warningFlag(st,{onChange:(v,t)=>rd(t)}), + '<b>the mechanical alarm flag.</b> A striped barber-pole slides into a window when the condition trips — not a lamp, a flag. Click to trip and clear it. After the VIB flag on an altimeter.'); +card(I,'R25','Fourteen-segment display', + (st,rd)=>GW.seg14(st,{onChange:(v,t)=>rd(t)}), + '<b>segments that spell.</b> The starburst alphanumeric — diagonals and split bars let it write words, not just digits. Click to cycle the word. After a pedal preset display.'); +card(I,'R26','Response graph', + (st,rd)=>GW.responseGraph(st,{onChange:(v,t)=>rd(t)}), + '<b>a curve on labeled axes.</b> Log frequency across, dB up the side, and the amber peak is yours to place — drag it anywhere on the plot. After the response display of a mastering processor.'); +card(I,'R30','Telegraph indicator', + (st,rd)=>GW.telegraphIndicator(st,{onChange:(v,t)=>rd(t)}), + '<b>state on a pie of sectors.</b> The pointer and its little flag name the active state, engine-telegraph style. Click to step the state. After an electric indicator dial.'); +card(I,'R31','Radar sweep', + (st,rd)=>GW.radarSweep(st,{onChange:(v,t)=>rd(t)}), + '<b>the rotating scan.</b> A sweep beam circles the bearing ring, contacts bloom as it passes and fade after. Click to mark the current bearing. After a radar PPI scope.'); +card(I,'R35','Day-date disc calendar', + (st,rd)=>GW.dayDateCal(st,{onChange:(v,t)=>rd(t)}), + '<b>two coaxial discs, one index.</b> The outer ring carries the date, the inner disc the weekday; both read where the yellow hand points. Starts on today; click to roll midnight forward and watch the discs step. After a watch day-date movement.'); +card(I,'R36','LED dot matrix', + (st,rd)=>GW.dotMatrix(st,{onChange:(v,t)=>rd(t)}), + '<b>a bitmap, one red dot at a time.</b> 64 discrete LEDs behind a tinted window — segments draw digits, but a matrix draws anything. Click dots to paint the pattern; the readout counts what is lit. After the K4816 pattern generator faceplate.'); +card(I,'R45','Flip-disc tile array', + (st,rd)=>GW.flipDisc(st,{onChange:(v,t)=>rd(t)}), + '<b>the mechanical pixel.</b> Each disc flips between its yellow face and its black back and then STAYS — no power, no glow, state that persists. Click a disc and watch it flip. The dot matrix glows; this one clacks. After transit flip-dot signs.'); +card(I,'R46','Dekatron counting ring', + (st,rd)=>GW.dekatron(st,{onChange:(v,t)=>rd(t)}), + '<b>a count as a position, and the counting is visible.</b> Each click is a pulse; the neon glow steps one cathode around the ring and carries on wrap. Nixies show the digit — the Dekatron shows the arithmetic. After glow-transfer counter tubes.'); +card(I,'R47','Landing gear indicator', + (st,rd)=>GW.gearIndicator(st,{onChange:(v,t)=>rd(t)}), + '<b>three greens or nothing.</b> A spatial lamp panel: nose and mains each get a light, green only when down and locked; the amber transit pulse means neither state. Click the wheel lever to cycle the gear. After a cockpit gear panel.'); +card(I,'R52','Blinkenlights front panel', + (st,rd)=>GW.blinkenlights(st,{onChange:(v,t)=>rd(t)}), + '<b>the machine thinking, out loud.</b> Address and data lamps ripple with the live computation; the switch register below is yours — flip bits and watch the pattern change. DIP switches hold config; this shows work happening. After a PDP-11/70 front panel.'); + +/* ============ CONTROL WIRING ============ */ +/* 01 toggle */ +/* 01-09, 24, 25, N01-N10 extracted to widgets.js (GW.*) — wired at their card records */ + +/* ============ METER WIRING (drag-driven) ============ */ +/* 10-17, N11-N18 extracted to widgets.js (GW.*) — wired at their card records */ + +/* ============ INDICATOR WIRING ============ */ +/* 18-23, 26 extracted to widgets.js (GW.*) — wired at their card records */ +/* N20-N28 extracted to widgets.js (GW.*) — wired at their card records */ + +/* ============ REFERENCE-BATCH (R) WIDGETS — SVG builders ============ */ +/* svgEl, polar, VUDB live in widgets.js (GW) */ + +/* R01 ballistics: the page owns the signal chase; the meter just takes set(t) */ +function tickMcVu(){const a=lvl();const target=Math.min(1.02,a*1.12); + const t=MH.mcvu.get();MH.mcvu.set(t+(target-t)*(target>t?0.32:0.10));} + +/* R02-R06, R12, R14, R15 extracted to widgets.js (GW.*) */ + + + + + + + + + +/* R07-R09 extracted to widgets.js (GW.*) */ + +/* R10, R11 extracted to widgets.js (GW.*) */ + + + + +/* R13, R17 extracted to widgets.js (GW.*) */ + +/* R25, R26, R30, R31 extracted to widgets.js (GW.*) */ + + + + +/* ============ LIVE SIGNAL LOOPS ============ */ +/* extracted meters: the page keeps the clock + demo signal, drives card handles */ +const MH={vu:$('card-11').gw,mini:$('card-12').gw,spark:$('card-16').gw,wave:$('card-17').gw, + scope:$('card-N11').gw,eq:$('card-N12').gw,strip:$('card-N16').gw,mcvu:$('card-R01').gw}; +const IH={flap:$('card-N20').gw,seven:$('card-N21').gw,vfd:$('card-N22').gw, + counter:$('card-N25').gw,clock:$('card-N26').gw}; + +let ph=0; +const eqBands=Array.from({length:11},(_,i)=>({v:0.4,ph:i*0.6})); +function lvl(){return Math.max(0,Math.min(1,0.5+0.4*Math.sin(ph*1.3)+(Math.random()<0.15?Math.random()*0.4:0)-Math.random()*0.08));} +function fastTick(){ + ph+=0.09; + const a=lvl(),b=lvl(); + MH.vu.set(a,b);MH.mini.set(a); + MH.spark.push(0.5+0.42*Math.sin(ph*0.9)+(Math.random()-0.5)*0.25); + const env=Math.min(1,0.6+0.3*Math.sin(ph*0.4)); + const smp=[];for(let x=0;x<=170;x+=3)smp.push(Math.sin(x*0.18+ph*3)*Math.sin(x*0.05)); + MH.wave.set(smp,env); + const senv=Math.min(1,0.6+0.3*Math.sin(ph*0.4)); + const ssmp=[];for(let x=0;x<=176;x+=3)ssmp.push(Math.sin(x*0.16+ph*3.2)*senv); + MH.scope.set(ssmp,0.6+0.3*Math.sin(ph*0.4)); + eqBands.forEach(bd=>{bd.ph+=0.16;bd.v=Math.max(0.05,Math.min(1,0.5+0.42*Math.sin(bd.ph)+(Math.random()<0.2?Math.random()*0.3:0)-Math.random()*0.06));}); + MH.eq.set(eqBands.map(bd=>bd.v)); + MH.strip.push((28+24*Math.sin(ph*0.7)*(0.6+0.4*Math.sin(ph*0.13))-(Math.random()-0.5)*4)/56); +} +/* indicator loops: builders own state + clicks; the page keeps the clocks and + the time source (tick contract) */ +function tickClock(){const d=new Date();IH.clock.set(d.getHours(),d.getMinutes(),d.getSeconds());} +function tickFlap(){IH.flap.next();} +function tickSeven(){IH.seven.tick();} +function tickT(){$('card-20').gw.tick();} +function tickVfd(){IH.vfd.tick();} +function tickCounter(){IH.counter.set(IH.counter.get()+137);} + +/* static paint for reduced motion */ +function paintStatic(){ + MH.vu.set(0.55,0.5);MH.mini.set(0.55); + MH.spark.fill(0.5); + MH.wave.set([],0.6); + eqBands.forEach((bd,i)=>bd.v=0.3+0.5*Math.abs(Math.sin(i*0.9))); + MH.eq.set(eqBands.map(bd=>bd.v)); + MH.scope.set(Array.from({length:60},(_,i)=>Math.sin(i*0.5)*18/22),0.74); + MH.strip.set(Array.from({length:60},(_,i)=>0.5+Math.sin(i*0.3)*16/56),0.5); + tickClock();IH.flap.set(0); + MH.mcvu.set(.5); +} + +if(!reduced){ + setInterval(fastTick,80); + setInterval(tickMcVu,80); + setInterval(tickClock,1000);tickClock(); + setInterval(tickFlap,1500); + setInterval(tickVfd,32); + setInterval(tickCounter,1600); + setInterval(tickSeven,1000); + setInterval(tickT,1000); + document.querySelectorAll('.reel.spin').forEach(r=>r.style.animationPlayState='running'); +}else{ + paintStatic(); + document.querySelectorAll('.reel.spin').forEach(r=>r.style.animationPlayState='paused'); +} + +/* R35, R36 extracted to widgets.js (GW.*) */ + + +/* R43, R44 extracted to widgets.js (GW.*) */ + +/* R45-R47, R52 extracted to widgets.js (GW.*) */ + + + + +/* R53-R56 extracted to widgets.js (GW.*) */ + +/* ---- screen-family chips: first round, the clearly-screen widgets ---- + Defaults match each widget's shipped color (var fallbacks), so no family is applied until a chip is clicked. + The nixie stays chipless on purpose: neon is only ever orange, so alternates would be historically false. */ +screenChips('R10',$('card-R10').gw.el,['amber','green','red','blue','vfd'],'amber'); +screenChips('R17',$('card-R17').gw.el,['green','amber','red','blue','vfd'],'green'); +screenChips('R19',$('card-R19').gw.el,['white','green','amber','blue','vfd'],'white'); +screenChips('R31',$('card-R31').gw.el,['amber','green','red','blue','vfd'],'amber'); +screenChips('N11',$('card-N11').gw.el,['green','amber','red','blue','vfd'],'green'); +BOOST.forEach(no=>{const rd=document.getElementById('rd-'+no); + if(rd)rd.closest('.card').querySelector('.stagew').classList.add('boost');}); + +/* cross-reference links: card IDs mentioned in notes and spec sheets jump to that card */ +(function(){const ids=new Set([...document.querySelectorAll('.card .no')].map(n=>n.textContent)); + document.querySelectorAll('.wnote,.igrid .iv').forEach(el=>{ + if(el.querySelector('a[href^="reference/"]'))return; // leave the photo-link rows alone + el.innerHTML=el.innerHTML.replace(/\b(R\d{2}|N\d{2})\b/g, + m=>ids.has(m)?`<a class="xref" href="#card-${m}">${m}</a>`:m);});})(); + +/* style chips: shared demo rig for builders with constructor style opts — + reads a builder's STYLES table and drives setStyle on the live card instance */ +function styleChips(no,STYLES,AXES){ + const h=$('card-'+no)?.gw; if(!h)return; + const cardEl=h.el.closest('.card'); + const row=document.createElement('div'); row.className='famchips'; + for(const [label,axis,def] of AXES){ + const g=document.createElement('span'); g.className='fgroup'; + const lab=document.createElement('span'); lab.className='lab'; lab.textContent=label; g.appendChild(lab); + const chips=[]; + for(const [name,o] of Object.entries(STYLES[axis])){ + const b=document.createElement('span'); b.className='fc'+(name===def?' on':''); + b.style.background=o.dot; b.title=name; chips.push(b); + b.addEventListener('click',()=>{h.setStyle(axis,name); + chips.forEach(x=>x.classList.toggle('on',x===b));}); + g.appendChild(b);} + row.appendChild(g);} + cardEl.querySelector('.opts').appendChild(row); +} +styleChips('01',GW.slideToggle.STYLES,[['on','on','amber'],['off','off','dark'],['off text','offText','white'],['thumb','thumb','light']]); +styleChips('R05',GW.filterBank.STYLES,[['panel','panel','silver'],['shape','caps','block'],['color','capColor','black']]); +styleChips('06',GW.segmented.STYLES,[['accent','accent','amber']]); +styleChips('25',GW.slideRule.STYLES,[['face','skin','warm']]); + +/* final tally pass: setV fires per card during build, but each card is still + detached at that moment, so the running counts lag by one — recount now */ +updateVTally(); +</script> +</body> +</html> |
