aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes/panel-widget-gallery.html
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-12 21:50:14 -0500
committerCraig Jennings <c@cjennings.net>2026-07-12 21:50:14 -0500
commit142f8180fe679ce00d212248cef54364c0ae5685 (patch)
treee2bb96f0155c9c245e046fbac8984c35ade514f3 /docs/prototypes/panel-widget-gallery.html
parent313496ba52424b883de3b528130a85382fa726c0 (diff)
downloadarchsetup-142f8180fe679ce00d212248cef54364c0ae5685.tar.gz
archsetup-142f8180fe679ce00d212248cef54364c0ae5685.zip
refactor(gallery): extract controls R27-R29, R32-R34, R37, R38 into GW builders
Winged gain selector, rotary disc switch, guarded toggle, mechanical timer dial, four-way rocker, four-way toggle selector, pin routing matrix, and dead-man button. The timer dial keeps its reduced-motion-gated 1 Hz wind-down inside the builder — it's widget behavior, not a gallery meter loop. discRed moves to the shared defs so the guarded toggle renders standalone. Behavioral pass now 96 checks; both probes green.
Diffstat (limited to 'docs/prototypes/panel-widget-gallery.html')
-rw-r--r--docs/prototypes/panel-widget-gallery.html313
1 files changed, 8 insertions, 305 deletions
diff --git a/docs/prototypes/panel-widget-gallery.html b/docs/prototypes/panel-widget-gallery.html
index 709b2e8..e08db62 100644
--- a/docs/prototypes/panel-widget-gallery.html
+++ b/docs/prototypes/panel-widget-gallery.html
@@ -1662,28 +1662,28 @@ 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',
- `<svg id="wingk" class="rsvg drag" viewBox="0 0 110 110" width="110" height="110"></svg>`,
+ (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',
- `<svg id="discsw" class="rsvg press" viewBox="0 0 110 116" width="110" height="116"></svg>`,
+ (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',
- `<svg id="guardsw" class="rsvg press" viewBox="0 0 90 100" width="90" height="100"></svg>`,
+ (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',
- `<svg id="mtimer" class="rsvg" viewBox="0 0 158 132" width="158" height="132"></svg>`,
+ (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',
- `<svg id="rocker4" class="rsvg press" viewBox="0 0 110 110" width="110" height="110"></svg>`,
+ (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',
- `<svg id="fourway" class="rsvg press" viewBox="0 0 130 130" width="130" height="130"></svg>`,
+ (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',
- `<svg id="pinmx" class="rsvg press" viewBox="0 0 160 110" width="160" height="110"></svg>`,
+ (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',
- `<svg id="deadman" class="rsvg" viewBox="0 0 120 110" width="120" height="110"></svg>`,
+ (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',
`<svg id="teldial" class="rsvg press" viewBox="0 0 130 130" width="130" height="130"></svg>`,
@@ -2299,87 +2299,6 @@ function tickRcrt(){rcrtPh+=0.12;const amp=8+3*Math.sin(rcrtPh*0.35);let pts='';
tickRcrt();
if(!matchMedia('(prefers-reduced-motion: reduce)').matches)setInterval(tickRcrt,80);
-/* R27 winged gain selector: red T-bar over a dot ring, stepped detents */
-const WINGK_STEPS=[-80,-70,-60,-50,-40,-30,-20,-10,-5,0,5,10];
-let wingI=7,wingGrp=null;
-(function(){const s=$('wingk'),cx=55,cy=52;
- const defs=svgEl(s,'defs',{});
- const rg2=svgEl(defs,'radialGradient',{id:'wingRed',cx:'40%',cy:'32%',r:'80%'});
- svgEl(rg2,'stop',{offset:'0','stop-color':'#d98a6f'});
- svgEl(rg2,'stop',{offset:'.6','stop-color':'var(--fail)'});
- svgEl(rg2,'stop',{offset:'1','stop-color':'#6e2415'});
- WINGK_STEPS.forEach((db,i)=>{const a=-135+i/(WINGK_STEPS.length-1)*270;
- const [dx,dy]=polar(cx,cy,38,a);
- svgEl(s,'circle',{cx:dx,cy:dy,r:1.5,fill:'var(--steel)'});
- if(i%2===1||db===0||db===10||db===-80){const [tx,ty]=polar(cx,cy,46.5,a);
- svgEl(s,'text',{x:tx,y:ty+2.2,'text-anchor':'middle','font-size':5.5,'font-family':'var(--mono)',
- fill:'var(--steel)'}).textContent=db>0?'+'+db:db;}});
- wingGrp=svgEl(s,'g',{});wingGrp.style.transition='transform .09s';
- svgEl(wingGrp,'rect',{x:cx-6.5,y:cy-30,width:13,height:60,rx:6,fill:'url(#wingRed)',stroke:'#4e150a','stroke-width':.8});
- svgEl(wingGrp,'circle',{cx,cy,r:15,fill:'url(#wingRed)',stroke:'#4e150a','stroke-width':.8});
- svgEl(wingGrp,'line',{x1:cx,y1:cy-28,x2:cx,y2:cy-18,stroke:'var(--cream)','stroke-width':2,'stroke-linecap':'round'});
- svgEl(wingGrp,'ellipse',{cx:cx-4,cy:cy-8,rx:5,ry:3,fill:'rgba(255,255,255,.18)',transform:`rotate(-30,${cx-4},${cy-8})`});})();
-function setWing(v){const i=Math.max(0,Math.min(WINGK_STEPS.length-1,Math.round(v)));wingI=i;
- wingGrp.style.transform=`rotate(${-135+i/(WINGK_STEPS.length-1)*270}deg)`;
- wingGrp.style.transformOrigin='55px 52px';
- const db=WINGK_STEPS[i];
- setRd('R27',(db>0?'+':'')+db+' dB');}
-dragDelta($('wingk'),()=>wingI,setWing,{min:0,max:WINGK_STEPS.length-1,sens:.05});setWing(7);
-
-/* R28 rotary disc switch: the whole disc turns between heavy positions */
-const DISCSW_POS=[['OFF',-45],['ON',45],['COMBINE',135]];
-let discI=1,discGrp=null,discLbls=[];
-(function(){const s=$('discsw'),cx=55,cy=58;
- const defs=svgEl(s,'defs',{});
- const rr=svgEl(defs,'radialGradient',{id:'discRed',cx:'42%',cy:'34%',r:'85%'});
- svgEl(rr,'stop',{offset:'0','stop-color':'#d98a6f'});
- svgEl(rr,'stop',{offset:'.65','stop-color':'var(--fail)'});
- svgEl(rr,'stop',{offset:'1','stop-color':'#7a2a1a'});
- svgEl(s,'circle',{cx,cy,r:48,fill:'#1a1714',stroke:'#060505','stroke-width':1.5});
- for(let k=0;k<4;k++){const [x,y]=polar(cx,cy,44,45+k*90);
- svgEl(s,'circle',{cx:x,cy:y,r:2.6,fill:'url(#sfScrew)',stroke:'#0a0908','stroke-width':.5});}
- DISCSW_POS.forEach(([lbl,a],i)=>{const [tx,ty]=polar(cx,cy,42,a);
- const t=svgEl(s,'text',{x:tx,y:ty+2.4,'text-anchor':'middle','font-size':5.8,'letter-spacing':'.06em',
- 'font-family':'var(--mono)',fill:'var(--dim)'});t.textContent=lbl;discLbls.push(t);});
- svgEl(s,'circle',{cx,cy,r:36,fill:'url(#discRed)',stroke:'#4e150a','stroke-width':1});
- discGrp=svgEl(s,'g',{});discGrp.style.transition='transform .15s';discGrp.style.transformOrigin=`${cx}px ${cy}px`;
- svgEl(discGrp,'rect',{x:cx-27,y:cy-8,width:54,height:16,rx:8,fill:'url(#discRed)',stroke:'#8f3520','stroke-width':1});
- svgEl(discGrp,'rect',{x:cx-25,y:cy-6.5,width:50,height:4,rx:2,fill:'rgba(255,255,255,.16)'});
- svgEl(discGrp,'line',{x1:cx+20,y1:cy,x2:cx+26,y2:cy,stroke:'var(--cream)','stroke-width':2.5,'stroke-linecap':'round'});
- svgEl(s,'ellipse',{cx:cx-12,cy:cy-14,rx:12,ry:6,fill:'rgba(255,255,255,.10)',transform:`rotate(-28,${cx-12},${cy-14})`});
- $('discsw').onclick=()=>setDisc((discI+1)%DISCSW_POS.length);})();
-function setDisc(i){discI=i;
- discGrp.style.transform=`rotate(${DISCSW_POS[i][1]}deg)`;
- discLbls.forEach((t,k)=>t.setAttribute('fill',k===i?'var(--gold-hi)':'var(--dim)'));
- setRd('R28',DISCSW_POS[i][0]);}
-setDisc(1);
-
-/* R29 guarded toggle: guard posts + red collar mark the critical throw */
-let guardOn=true,guardLever=null,guardLblOn=null,guardLblOff=null;
-(function(){const s=$('guardsw'),cx=45,cy=52;
- guardLblOn=svgEl(s,'text',{x:cx,y:12,'text-anchor':'middle','font-size':7,'letter-spacing':'.1em',
- 'font-family':'var(--mono)',fill:'var(--gold-hi)'});guardLblOn.textContent='ON';
- guardLblOff=svgEl(s,'text',{x:cx,y:96,'text-anchor':'middle','font-size':7,'letter-spacing':'.1em',
- 'font-family':'var(--mono)',fill:'var(--dim)'});guardLblOff.textContent='OFF';
- svgEl(s,'circle',{cx,cy,r:13,fill:'url(#discRed)',stroke:'#4e150a','stroke-width':1});
- const pts=[];for(let k=0;k<6;k++){const a=(k*60+30)*Math.PI/180;pts.push((cx+11*Math.cos(a))+','+(cy+11*Math.sin(a)));}
- svgEl(s,'polygon',{points:pts.join(' '),fill:'url(#nutG)',stroke:'#2b2822'});
- svgEl(s,'circle',{cx,cy,r:6,fill:'#14110e',stroke:'#000'});
- for(const gx of [16,74]){
- svgEl(s,'rect',{x:gx-6,y:cy-24,width:12,height:48,rx:6,fill:'url(#nutG)',stroke:'#2b2822','stroke-width':1});
- svgEl(s,'rect',{x:gx-4,y:cy-21,width:3.5,height:42,rx:2,fill:'rgba(255,255,255,.14)'});}
- guardLever=svgEl(s,'g',{});
- svgEl(guardLever,'path',{d:`M ${cx-3} ${cy} L ${cx-2} 26 L ${cx+2} 26 L ${cx+3} ${cy} Z`,fill:'url(#chromeG)',stroke:'#4e4a42','stroke-width':.5});
- svgEl(guardLever,'rect',{x:cx-4.5,y:16,width:9,height:12,rx:2,fill:'url(#tipG)',stroke:'#5e5a52','stroke-width':.6});
- for(let r=0;r<4;r++)svgEl(guardLever,'line',{x1:cx-3.5,y1:18.5+r*2.4,x2:cx+3.5,y2:18.5+r*2.4,stroke:'rgba(0,0,0,.25)','stroke-width':.8});
- guardLever.style.transformOrigin=`${cx}px ${cy}px`;guardLever.style.transition='transform .12s';
- $('guardsw').onclick=()=>setGuard(!guardOn);})();
-function setGuard(on){guardOn=on;guardLever.style.transform=on?'rotate(0deg)':'rotate(180deg)';
- guardLblOn.setAttribute('fill',on?'var(--gold-hi)':'var(--dim)');
- guardLblOff.setAttribute('fill',on?'var(--dim)':'var(--gold-hi)');
- setRd('R29',on?'ON':'OFF');}
-setGuard(true);
-
/* R30 telegraph indicator: the pointer names the state on labeled sectors */
const TELIND_STATES=['OK','TEST','BUSY','STOP','ATTN','FAULT'];
let telI=0,telPtr=null;
@@ -2648,162 +2567,6 @@ if(!reduced){
document.querySelectorAll('.reel.spin').forEach(r=>r.style.animationPlayState='paused');
}
-/* R32 mechanical timer dial: dial rotates under a fixed index; wind by drag, stop by the red knob */
-let mtMin=0,mtRot=null;
-(function(){const s=$('mtimer'),cx=62,cy=58;
- const defs=svgEl(s,'defs',{});
- const fc=svgEl(defs,'radialGradient',{id:'mtFace',cx:'50%',cy:'42%',r:'75%'});
- svgEl(fc,'stop',{offset:'0','stop-color':'#282320'});
- svgEl(fc,'stop',{offset:'1','stop-color':'#14110e'});
- const rd=svgEl(defs,'radialGradient',{id:'mtRed',cx:'38%',cy:'32%',r:'80%'});
- svgEl(rd,'stop',{offset:'0','stop-color':'#e0523a'});
- svgEl(rd,'stop',{offset:'1','stop-color':'#8f2416'});
- const hb=svgEl(defs,'linearGradient',{id:'mtHub',x1:0,y1:0,x2:0,y2:1});
- svgEl(hb,'stop',{offset:'0','stop-color':'#c9c4b8'});
- svgEl(hb,'stop',{offset:'1','stop-color':'#6e685c'});
- // bezel + knurled coin edge
- svgEl(s,'circle',{cx,cy,r:50,fill:'#17140f',stroke:'#060505','stroke-width':2});
- svgEl(s,'circle',{cx,cy,r:47.5,fill:'none',stroke:'#3a352c','stroke-width':4,'stroke-dasharray':'2 1.6'});
- svgEl(s,'circle',{cx,cy,r:44,fill:'url(#mtFace)',stroke:'#0a0908','stroke-width':1});
- // rotating scale: OFF at 0, minutes climb clockwise at 4.5 deg/min (60 at 270)
- mtRot=svgEl(s,'g',{});
- for(let m=0;m<=60;m+=5){const major=m%20===0;
- const [x1,y1]=polar(cx,cy,40,m*4.5),[x2,y2]=polar(cx,cy,major?34:37,m*4.5);
- svgEl(mtRot,'line',{x1,y1,x2,y2,stroke:'var(--cream)','stroke-width':major?1.2:.55,opacity:major?.9:.55});}
- for(const m of [20,40,60]){const [x,y]=polar(cx,cy,27,m*4.5);
- svgEl(mtRot,'text',{x,y:y+3,'text-anchor':'middle','font-size':9,'font-weight':700,
- 'font-family':'var(--mono)',fill:'var(--cream)'}).textContent=String(m);}
- const [ox,oy]=polar(cx,cy,27,0);
- svgEl(mtRot,'text',{x:ox,y:oy+3,'text-anchor':'middle','font-size':7.5,'font-weight':700,
- 'font-family':'var(--mono)',fill:'#e0523a'}).textContent='OFF';
- const [mx,my]=polar(cx,cy,16,180);
- svgEl(mtRot,'text',{x:mx,y:my+2.5,'text-anchor':'middle','font-size':5.4,'letter-spacing':'.14em',
- 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent='MINUTES';
- // hub with screw slot
- svgEl(s,'circle',{cx,cy,r:9,fill:'url(#mtHub)',stroke:'#3c382f','stroke-width':1});
- svgEl(s,'line',{x1:cx-5.5,y1:cy,x2:cx+5.5,y2:cy,stroke:'#4a4438','stroke-width':2});
- svgEl(s,'circle',{cx,cy,r:2.2,fill:'#8f897b'});
- // fixed red index at top + winding-direction arrow
- svgEl(s,'line',{x1:cx,y1:cy-44,x2:cx,y2:cy-32,stroke:'#e0523a','stroke-width':1.6});
- svgEl(s,'path',{d:'M 122 14 A 26 26 0 0 1 136 26',fill:'none',stroke:'var(--steel)','stroke-width':1.6});
- svgEl(s,'polygon',{points:'136,26 130.5,24.5 134.5,19.5',fill:'var(--steel)'});
- // engraved plate captions
- const cap=(t,y)=>svgEl(s,'text',{x:4,y,'font-size':6.2,'letter-spacing':'.1em',
- 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent=t;
- cap('PUSH TO STOP',118); cap('TURN TO START',128);
- // dial drag surface (under the knob in DOM so the knob wins the overlap)
- const hit=svgEl(s,'circle',{cx,cy,r:50,fill:'transparent'});
- hit.style.cursor='grab';
- dragDelta(hit,()=>mtMin,v=>setMtimer(v),{min:0,max:60});
- // red stop knob: fluted edge, domed center
- const knob=svgEl(s,'g',{});
- svgEl(knob,'circle',{cx:126,cy:104,r:20,fill:'url(#mtRed)',stroke:'#5c150c','stroke-width':1.5});
- svgEl(knob,'circle',{cx:126,cy:104,r:18,fill:'none',stroke:'rgba(0,0,0,.4)','stroke-width':3.4,'stroke-dasharray':'3 2.4'});
- svgEl(knob,'circle',{cx:126,cy:104,r:8.5,fill:'url(#mtRed)',stroke:'rgba(0,0,0,.3)','stroke-width':.8});
- svgEl(knob,'ellipse',{cx:121,cy:98,rx:6,ry:3.4,fill:'rgba(255,255,255,.18)',transform:'rotate(-28,121,98)'});
- knob.style.cursor='pointer';
- knob.addEventListener('click',()=>{setMtimer(0);setRd('R32','STOP · OFF');});})();
-function setMtimer(m){mtMin=Math.max(0,Math.min(60,m));
- mtRot.setAttribute('transform',`rotate(${(-mtMin*4.5).toFixed(2)},62,58)`);
- setRd('R32',mtMin>0.5?'T-'+Math.round(mtMin)+' MIN':'OFF');}
-setMtimer(0);
-if(!matchMedia('(prefers-reduced-motion: reduce)').matches)
- setInterval(()=>{if(mtMin>0){setMtimer(mtMin-1);if(mtMin===0)setRd('R32','DING · OFF');}},1000);
-
-/* R33 four-way rocker: quadrant clicks step a tracked cursor; arrows flash on press */
-(function(){const s=$('rocker4'),cx=55,cy=55;
- const defs=svgEl(s,'defs',{});
- const pd=svgEl(defs,'radialGradient',{id:'rk4Pad',cx:'42%',cy:'34%',r:'85%'});
- svgEl(pd,'stop',{offset:'0','stop-color':'#33302b'});
- svgEl(pd,'stop',{offset:'1','stop-color':'#141210'});
- const ar=svgEl(defs,'linearGradient',{id:'rk4Arr',x1:0,y1:0,x2:0,y2:1});
- svgEl(ar,'stop',{offset:'0','stop-color':'#e9e4d6'});
- svgEl(ar,'stop',{offset:'1','stop-color':'#9d988b'});
- const nb=svgEl(defs,'radialGradient',{id:'rk4Nub',cx:'38%',cy:'30%',r:'85%'});
- svgEl(nb,'stop',{offset:'0','stop-color':'#4a453e'});
- svgEl(nb,'stop',{offset:'1','stop-color':'#0d0c0a'});
- // recessed plate + rubber pad
- svgEl(s,'rect',{x:3,y:3,width:104,height:104,rx:18,fill:'#211e1a',stroke:'#0c0b09','stroke-width':1.5});
- svgEl(s,'circle',{cx,cy,r:45,fill:'#0f0d0b'});
- svgEl(s,'circle',{cx,cy,r:41,fill:'url(#rk4Pad)',stroke:'#060505','stroke-width':1});
- // four arrows, pointing outward
- const ARR={up:'55,22 45,36 65,36',down:'55,88 45,74 65,74',left:'22,55 36,45 36,65',right:'88,55 74,45 74,65'};
- const arrows={};
- for(const [dir,pts] of Object.entries(ARR))
- arrows[dir]=svgEl(s,'polygon',{points:pts,fill:'url(#rk4Arr)',stroke:'#3c382f','stroke-width':.7});
- // center pivot nub
- svgEl(s,'circle',{cx,cy,r:7,fill:'url(#rk4Nub)',stroke:'#060505','stroke-width':.8});
- svgEl(s,'ellipse',{cx:cx-2,cy:cy-2.5,rx:2.6,ry:1.7,fill:'rgba(255,255,255,.25)'});
- // quadrant hit zones (drawn last so they win), press flash + tracked position
- let rx=0,ry=0;
- const press=(dir,dx,dy)=>{rx+=dx;ry+=dy;
- setRd('R33',dir.toUpperCase()+' · x '+rx+' y '+ry);
- arrows[dir].setAttribute('fill','var(--gold-hi)');
- setTimeout(()=>arrows[dir].setAttribute('fill','url(#rk4Arr)'),160);};
- const zone=(dir,pts,dx,dy)=>{const z=svgEl(s,'polygon',{points:pts,fill:'transparent'});
- z.style.cursor='pointer';z.addEventListener('click',()=>press(dir,dx,dy));};
- zone('up',`${cx},${cy} 20,20 90,20`,0,1);
- zone('down',`${cx},${cy} 20,90 90,90`,0,-1);
- zone('left',`${cx},${cy} 20,20 20,90`,-1,0);
- zone('right',`${cx},${cy} 90,20 90,90`,1,0);
- setRd('R33','x 0 y 0');})();
-
-/* R34 four-way toggle selector: ball lever throws to a diagonal; corner lamps show the state */
-(function(){const s=$('fourway'),cx=65,cy=65;
- const defs=svgEl(s,'defs',{});
- const ch=svgEl(defs,'linearGradient',{id:'fw4Chrome',x1:0,y1:0,x2:1,y2:1});
- svgEl(ch,'stop',{offset:'0','stop-color':'#e8e6e0'});
- svgEl(ch,'stop',{offset:'.5','stop-color':'#9a968c'});
- svgEl(ch,'stop',{offset:'1','stop-color':'#55524a'});
- const bl=svgEl(defs,'radialGradient',{id:'fw4Ball',cx:'36%',cy:'30%',r:'80%'});
- svgEl(bl,'stop',{offset:'0','stop-color':'#f2f0ea'});
- svgEl(bl,'stop',{offset:'.55','stop-color':'#8e8a80'});
- svgEl(bl,'stop',{offset:'1','stop-color':'#3c3a34'});
- // printed panel graphics: axes, circle, diagonal square, labels
- svgEl(s,'line',{x1:8,y1:cy,x2:122,y2:cy,stroke:'var(--steel)','stroke-width':.7,opacity:.7});
- svgEl(s,'line',{x1:cx,y1:8,x2:cx,y2:122,stroke:'var(--steel)','stroke-width':.7,opacity:.7});
- svgEl(s,'text',{x:cx+2,y:13,'font-size':7,'font-family':'var(--mono)',fill:'var(--steel)'}).textContent='Y';
- svgEl(s,'text',{x:116,y:cy-4,'font-size':7,'font-family':'var(--mono)',fill:'var(--steel)'}).textContent='X';
- svgEl(s,'circle',{cx,cy,r:42,fill:'none',stroke:'var(--steel)','stroke-width':.9,opacity:.8});
- const QUAD={A:315,B:45,C:135,D:225};
- const diag=Object.values(QUAD).map(a=>polar(cx,cy,42,a));
- svgEl(s,'polygon',{points:diag.map(p=>p.join(',')).join(' '),fill:'none',stroke:'var(--steel)','stroke-width':.8,opacity:.8});
- const LAMP_AT={A:[14,14],B:[116,14],C:[116,116],D:[14,116]};
- const lamps={};
- for(const [q,a] of Object.entries(QUAD)){
- const [lx,ly]=polar(cx,cy,42,a);
- const [px,py]=LAMP_AT[q];
- svgEl(s,'line',{x1:lx,y1:ly,x2:px,y2:py,stroke:'var(--steel)','stroke-width':.7,opacity:.5});
- svgEl(s,'circle',{cx:lx,cy:ly,r:6.5,fill:'#14110e',stroke:'var(--steel)','stroke-width':.9});
- svgEl(s,'text',{x:lx,y:ly+2.8,'text-anchor':'middle','font-size':7.5,'font-weight':700,
- 'font-family':'var(--mono)',fill:'var(--cream)'}).textContent=q;
- svgEl(s,'circle',{cx:px,cy:py,r:5.5,fill:'#0d0b09',stroke:'#2c2820','stroke-width':1});
- lamps[q]=svgEl(s,'circle',{cx:px,cy:py,r:3.6,fill:'#3a3426'});}
- // bezel + boot
- svgEl(s,'circle',{cx,cy,r:20,fill:'#0d0c0a',stroke:'#2a2722','stroke-width':1.5});
- svgEl(s,'circle',{cx,cy,r:15,fill:'#17140f',stroke:'#060505','stroke-width':1});
- // lever: shaft + grip rotate to the selected diagonal; ball pivot stays centered
- const lever=svgEl(s,'g',{});
- svgEl(lever,'polygon',{points:`${cx-3},${cy} ${cx+3},${cy} ${cx+2},${cy-26} ${cx-2},${cy-26}`,fill:'url(#fw4Chrome)',stroke:'#3c3a34','stroke-width':.6});
- svgEl(lever,'rect',{x:cx-3.4,y:cy-40,width:6.8,height:16,rx:3,fill:'url(#fw4Chrome)',stroke:'#3c3a34','stroke-width':.6});
- svgEl(s,'circle',{cx,cy,r:8.5,fill:'url(#fw4Ball)',stroke:'#26241f','stroke-width':.8});
- svgEl(s,'ellipse',{cx:cx-2.6,cy:cy-3,rx:2.8,ry:1.9,fill:'rgba(255,255,255,.5)'});
- let fwPos=null;
- const setFw=q=>{fwPos=q;
- lever.setAttribute('transform',`rotate(${QUAD[q]},${cx},${cy})`);
- for(const k of Object.keys(QUAD)){
- lamps[k].setAttribute('fill',k===q?'var(--jewel-a)':'#3a3426');
- lamps[k].setAttribute('filter',k===q?'drop-shadow(0 0 4px rgba(255,180,58,.8))':'none');}
- setRd('R34','POS '+q);};
- // quadrant click zones (diagonal quadrants, drawn last)
- const zone=(q,pts)=>{const z=svgEl(s,'polygon',{points:pts,fill:'transparent'});
- z.style.cursor='pointer';z.addEventListener('click',()=>setFw(q));};
- zone('A',`${cx},${cy} ${cx},5 5,5 5,${cy}`);
- zone('B',`${cx},${cy} ${cx},5 125,5 125,${cy}`);
- zone('C',`${cx},${cy} 125,${cy} 125,125 ${cx},125`);
- zone('D',`${cx},${cy} ${cx},125 5,125 5,${cy}`);
- setFw('C');})();
-
/* R35 day-date disc calendar: coaxial printed discs rotate so today reads under the fixed hand */
const DD_DAYS=['SUN','MON','TUE','WED','THU','FRI','SAT'];
let ddDate=1,ddDay=0,ddDateG=null,ddDayG=null;
@@ -2862,66 +2625,6 @@ function setDdcal(d,w){ddDate=d;ddDay=w;const HAND=135;
setRd('R36',lit+' / 64 lit');});}
setRd('R36',lit+' / 64 lit');})();
-/* R37 pin routing matrix: click an intersection to seat/pull a pin; many-to-many */
-(function(){const s=$('pinmx');
- const ROWS=['OSC1','OSC2','LFO','NOIS','ENV'],COLS=['VCF','VCA','PAN','DLY','OUT','MOD'];
- const X0=42,Y0=30,DX=19,DY=15.5;
- svgEl(s,'rect',{x:2,y:2,width:156,height:106,rx:8,fill:'#1c1916',stroke:'#060505','stroke-width':1.5});
- COLS.forEach((c,j)=>svgEl(s,'text',{x:X0+j*DX,y:16,'text-anchor':'middle','font-size':5.4,
- 'letter-spacing':'.05em','font-family':'var(--mono)',fill:'var(--steel)'}).textContent=c);
- ROWS.forEach((r,i)=>svgEl(s,'text',{x:34,y:Y0+i*DY+2,'text-anchor':'end','font-size':5.4,
- 'letter-spacing':'.05em','font-family':'var(--mono)',fill:'var(--steel)'}).textContent=r);
- const pins=new Set(['OSC1>VCF','LFO>PAN','ENV>VCA']); // a legible default patch
- ROWS.forEach((r,i)=>COLS.forEach((c,j)=>{
- const cx=X0+j*DX,cy=Y0+i*DY,key=r+'>'+c;
- svgEl(s,'circle',{cx,cy,r:3.4,fill:'#0a0908',stroke:'#2c2820','stroke-width':1});
- const cap=svgEl(s,'circle',{cx,cy,r:4.4,fill:'var(--gold)',stroke:'#7d5c16','stroke-width':1,
- opacity:pins.has(key)?1:0,filter:'drop-shadow(0 1px 2px rgba(0,0,0,.6))'});
- const hit=svgEl(s,'circle',{cx,cy,r:8,fill:'transparent'});
- hit.style.cursor='pointer';
- hit.addEventListener('click',()=>{const on=!pins.has(key);
- if(on)pins.add(key);else pins.delete(key);
- cap.setAttribute('opacity',on?1:0);
- setRd('R37',(on?r+' → '+c:'pulled '+r+' → '+c)+' · '+pins.size+' routes');});}));
- setRd('R37',pins.size+' routes');})();
-
-/* R38 dead-man button: state exists only while the pointer holds it down */
-(function(){const s=$('deadman'),cx=60,cy=58;
- const defs=svgEl(s,'defs',{});
- const rb=svgEl(defs,'radialGradient',{id:'dmBtn',cx:'40%',cy:'32%',r:'85%'});
- svgEl(rb,'stop',{offset:'0','stop-color':'#3a3631'});
- svgEl(rb,'stop',{offset:'1','stop-color':'#16130f'});
- svgEl(s,'rect',{x:4,y:4,width:112,height:102,rx:12,fill:'#211e1a',stroke:'#0c0b09','stroke-width':1.5});
- svgEl(s,'text',{x:cx,y:18,'text-anchor':'middle','font-size':6.2,'letter-spacing':'.16em',
- 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent='HOLD TO RUN';
- const lamp=svgEl(s,'circle',{cx:104,cy:16,r:4.5,fill:'#1e2a12'});
- const ring=svgEl(s,'circle',{cx,cy,r:33,fill:'none',stroke:'#2c2820','stroke-width':3});
- const btn=svgEl(s,'g',{});
- svgEl(btn,'circle',{cx,cy,r:28,fill:'url(#dmBtn)',stroke:'#060505','stroke-width':1.5});
- svgEl(btn,'circle',{cx,cy,r:22,fill:'none',stroke:'rgba(255,255,255,.07)','stroke-width':1});
- svgEl(btn,'text',{x:cx,y:cy+2.5,'text-anchor':'middle','font-size':7,'letter-spacing':'.1em',
- 'font-family':'var(--mono)',fill:'var(--silver)'}).textContent='RUN';
- btn.style.cursor='pointer';
- let dmT0=null,dmIv=null;
- const stop=()=>{if(dmT0===null)return;
- const held=((Date.now()-dmT0)/1000).toFixed(1);
- dmT0=null;clearInterval(dmIv);
- btn.removeAttribute('transform');
- ring.setAttribute('stroke','#2c2820');ring.removeAttribute('filter');
- lamp.setAttribute('fill','#1e2a12');lamp.removeAttribute('filter');
- setRd('R38','SAFE (held '+held+'s)');};
- btn.addEventListener('pointerdown',e=>{btn.setPointerCapture(e.pointerId);
- dmT0=Date.now();
- btn.setAttribute('transform','translate(0,1.5)');
- ring.setAttribute('stroke','var(--gold)');ring.setAttribute('filter','drop-shadow(0 0 5px rgba(var(--glow-lo),.7))');
- lamp.setAttribute('fill','var(--pass)');lamp.setAttribute('filter','drop-shadow(0 0 4px rgba(116,147,47,.8))');
- setRd('R38','RUNNING 0.0s');
- dmIv=setInterval(()=>{if(dmT0!==null)setRd('R38','RUNNING '+((Date.now()-dmT0)/1000).toFixed(1)+'s');},100);
- e.preventDefault();});
- btn.addEventListener('pointerup',stop);
- btn.addEventListener('pointercancel',stop);
- setRd('R38','SAFE');})();
-
/* R39 rotary telephone dial: click a hole; the wheel winds to the stop and returns */
(function(){const s=$('teldial'),cx=65,cy=63;
const DIGITS=['1','2','3','4','5','6','7','8','9','0'];