From 9758f1710161b0353654e83da04af8e13253e561 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 12 Jul 2026 21:54:42 -0500 Subject: refactor(gallery): extract controls R39-R42, R48-R51 into GW builders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rotary telephone dial, circuit breaker panel, DSKY, cam-timer drum, knife switch, decade box, two-hand safety, and voice-loop keyset — the Controls section is now fully extracted. The dial keeps its wind-and-return animation (reduced-motion path included); the cam timer and voice loop keep their gated self-advance and activity-flicker intervals inside their builders. DSKY and voice loop are the first HTML-based builders with structural CSS still gallery-side. Behavioral pass now 118 checks; both probes green. --- docs/prototypes/panel-widget-gallery.html | 279 +----------------------------- 1 file changed, 8 insertions(+), 271 deletions(-) (limited to 'docs/prototypes/panel-widget-gallery.html') diff --git a/docs/prototypes/panel-widget-gallery.html b/docs/prototypes/panel-widget-gallery.html index e08db62..082c503 100644 --- a/docs/prototypes/panel-widget-gallery.html +++ b/docs/prototypes/panel-widget-gallery.html @@ -1686,28 +1686,28 @@ card(C,'R38','Dead-man button', (st,rd)=>GW.deadMan(st,{onChange:(v,t)=>rd(t)}), 'active only while held. 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)}), 'numeric entry embodied as time. 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)}), 'the first control the system can throw. 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)}), 'a command grammar, not a button per command. 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)}), 'a procedure as a rotating position. 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)}), 'the visible disconnect. 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)}), 'a number composed a digit at a time. 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)}), 'both hands, deliberately. 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)}), 'attention management, not selection. 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 ============ */ @@ -2625,150 +2625,6 @@ function setDdcal(d,w){ddDate=d;ddDay=w;const HAND=135; setRd('R36',lit+' / 64 lit');});} setRd('R36',lit+' / 64 lit');})(); -/* 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']; - const angOf=i=>(60-i*30+360)%360; // 1 at 60cw, counterclockwise to 0 at 150 - const STOP=105; - const defs=svgEl(s,'defs',{}); - const mt=svgEl(defs,'radialGradient',{id:'tdWheel',cx:'42%',cy:'36%',r:'85%'}); - svgEl(mt,'stop',{offset:'0','stop-color':'#8f8a7e'}); - svgEl(mt,'stop',{offset:'1','stop-color':'#4a463e'}); - svgEl(s,'circle',{cx,cy,r:56,fill:'#14110e',stroke:'#060505','stroke-width':2}); - svgEl(s,'circle',{cx,cy,r:50,fill:'#1e1a16',stroke:'#0a0908','stroke-width':1}); - // rotating finger wheel: ring + hole openings (dark wells punched in the metal) - const wheel=svgEl(s,'g',{}); - svgEl(wheel,'circle',{cx,cy,r:46,fill:'none',stroke:'url(#tdWheel)','stroke-width':17,opacity:.95}); - DIGITS.forEach((d,i)=>{const [x,y]=polar(cx,cy,38,angOf(i)); - svgEl(wheel,'circle',{cx:x,cy:y,r:7.2,fill:'#1e1a16',stroke:'#26221c','stroke-width':1.4});}); - // fixed digits ON TOP so they read through the holes (they stay put while the wheel spins) - DIGITS.forEach((d,i)=>{const [x,y]=polar(cx,cy,38,angOf(i)); - svgEl(s,'text',{x,y:y+3,'text-anchor':'middle','font-size':8.5,'font-weight':700, - 'font-family':'var(--mono)',fill:'var(--cream)'}).textContent=d;}); - svgEl(s,'circle',{cx,cy,r:16,fill:'url(#tdWheel)',stroke:'#26221c','stroke-width':1}); - // finger stop - const [fx,fy]=polar(cx,cy,52,STOP); - svgEl(s,'rect',{x:fx-2.5,y:fy-8,width:5,height:16,rx:2,fill:'#b8b2a4',stroke:'#3c382f','stroke-width':1, - transform:`rotate(${STOP},${fx},${fy})`}); - let dialed='',spinning=false; - const setW=a=>wheel.setAttribute('transform',`rotate(${a},${cx},${cy})`); - const finish=d=>{dialed=(dialed+d).slice(-10);setRd('R39',dialed);spinning=false;}; - DIGITS.forEach((d,i)=>{const [x,y]=polar(cx,cy,38,angOf(i)); - const hit=svgEl(s,'circle',{cx:x,cy:y,r:8.5,fill:'transparent'}); - hit.style.cursor='pointer'; - hit.addEventListener('click',()=>{if(spinning)return;spinning=true; - const R=(STOP-angOf(i)+360)%360; - if(matchMedia('(prefers-reduced-motion: reduce)').matches){setW(R);setTimeout(()=>{setW(0);finish(d);},160);return;} - const t0=performance.now(),Tf=R/300*1000,Tb=R/450*1000; - const step=t=>{const el=t-t0; - if(el{let closed=0,trip=null; - brk.forEach(b=>{if(b.state==='on')closed++;if(b.state==='tripped')trip=b.name; - b.lamp.setAttribute('fill',b.state==='on'?'var(--pass)':'#1e2a12'); - b.lamp.setAttribute('filter',b.state==='on'?'drop-shadow(0 0 3px rgba(116,147,47,.8))':'none'); - b.handle.setAttribute('y',b.state==='on'?40:b.state==='tripped'?57:74); - b.collar.setAttribute('opacity',b.state==='tripped'?1:0);}); - setRd('R40',closed+'/4 closed'+(trip?' · '+trip+' TRIPPED':''));}; - NAMES.forEach((name,i)=>{const x=14+i*30; - svgEl(s,'rect',{x,y:36,width:20,height:52,rx:4,fill:'#14110e',stroke:'#2c2820','stroke-width':1}); - const lamp=svgEl(s,'circle',{cx:x+10,cy:26,r:4,fill:'#1e2a12'}); - const collar=svgEl(s,'rect',{x:x+4,y:69,width:12,height:5,fill:'var(--amber-warn)',opacity:0}); - const handle=svgEl(s,'rect',{x:x+4,y:40,width:12,height:14,rx:2,fill:'#3a3631',stroke:'#060505','stroke-width':1}); - svgEl(s,'text',{x:x+10,y:98,'text-anchor':'middle','font-size':5.4,'letter-spacing':'.05em', - 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent=name; - const hit=svgEl(s,'rect',{x,y:20,width:20,height:80,fill:'transparent'}); - hit.style.cursor='pointer'; - const b={name,state:i<3?'on':'off',lamp,collar,handle}; - hit.addEventListener('click',()=>{b.state=b.state==='on'?'off':b.state==='off'?'on':'off';draw();}); - brk.push(b);}); - const tb=svgEl(s,'g',{}); - svgEl(tb,'rect',{x:132,y:40,width:22,height:16,rx:4,fill:'var(--fail)',stroke:'#5c150c','stroke-width':1}); - svgEl(tb,'text',{x:143,y:50.5,'text-anchor':'middle','font-size':6,'font-weight':700, - 'font-family':'var(--mono)',fill:'var(--cream)'}).textContent='TRIP'; - tb.style.cursor='pointer'; - tb.addEventListener('click',()=>{for(let i=brk.length-1;i>=0;i--)if(brk[i].state==='on'){brk[i].state='tripped';break;}draw();}); - draw();})(); - -/* R41 DSKY: verb/noun command grammar with status lamps and a lamp-test verb */ -(function(){const host=$('dsky'); - const LAMPS=['UPLINK','TEMP','GIMBAL','PROG','RESTART','OPR ERR']; - host.innerHTML= - `
${LAMPS.map(l=>`
${l}
`).join('')}
`+ - `
`+ - `
PROG
`+ - `
VERB
`+ - `
NOUN
`+ - `
`; - const win=w=>host.querySelector(`[data-w="${w}"]`); - const setWin=(w,txt)=>{win(w).querySelector('.wd').innerHTML=seg7(txt[0]||' ')+seg7(txt[1]||' ');}; - const lampEl=l=>host.querySelector(`[data-l="${l}"]`); - let mode=null,vals={prog:' ',verb:' ',noun:' '},entry=''; - const KEYS=['VERB','NOUN','CLR','ENTR','RSET','7','8','9','4','5','6','1','2','3','0']; - const pad=host.querySelector('.pad'); - const hot=()=>{host.querySelectorAll('.win').forEach(w=>w.classList.toggle('hot',w.dataset.w===mode));}; - const oprErr=()=>{lampEl('OPR ERR').classList.add('on');setTimeout(()=>lampEl('OPR ERR').classList.remove('on'),1200);setRd('R41','OPR ERR');}; - const commit=()=>{ - if(vals.verb.trim().length<2){oprErr();return;} - const v=vals.verb,n=vals.noun.trim(); - if(v==='35'){LAMPS.forEach(l=>lampEl(l).classList.add('on')); - setTimeout(()=>LAMPS.forEach(l=>lampEl(l).classList.remove('on')),1400); - setRd('R41','V35 · lamp test');} - else if(v==='16'&&n==='36'){vals.prog='16';setWin('prog',vals.prog);setRd('R41','V16 N36 · monitor clock');} - else setRd('R41','V'+v+(n?' N'+n:'')+' ENTR'); - vals.verb=' ';vals.noun=' ';entry='';mode=null;hot();}; - KEYS.forEach(k=>{const b=document.createElement('button');b.className='key';b.textContent=k; - b.addEventListener('click',()=>{ - if(k==='VERB'||k==='NOUN'){mode=k.toLowerCase();entry='';vals[mode]=' ';setWin(mode,' ');hot();} - else if(k==='CLR'){if(mode){vals[mode]=' ';entry='';setWin(mode,' ');}} - else if(k==='RSET'){LAMPS.forEach(l=>lampEl(l).classList.remove('on'));setRd('R41','RSET');} - else if(k==='ENTR')commit(); - else if(/\d/.test(k)){if(!mode){oprErr();return;} - entry=(entry+k).slice(0,2);vals[mode]=entry.padEnd(2,' ');setWin(mode,vals[mode]);}}); - pad.appendChild(b);}); - vals.prog='00';setWin('prog',vals.prog);setWin('verb',' ');setWin('noun',' '); - setRd('R41','VERB ## ENTR');})(); - -/* R42 cam-timer program drum: the program is a ring; the pointer self-advances through it */ -const CAM_STEPS=['OFF','FILL','WASH','WASH','WASH','RINSE','RINSE','DRAIN','SPIN','SPIN','FLUFF','COOL']; -const CAM_COLS={OFF:'#3a3426',FILL:'#54677d',WASH:'#d29638',RINSE:'#46b89e',DRAIN:'#969385',SPIN:'#cb6b4d',FLUFF:'#c9b98a',COOL:'#7c99b0'}; -let camPos=0,camPtr=null; -(function(){const s=$('camtimer'),cx=65,cy=65; - svgEl(s,'circle',{cx,cy,r:60,fill:'#17140f',stroke:'#060505','stroke-width':2}); - CAM_STEPS.forEach((st,i)=>{const a0=i*30-90,a1=a0+30; - const p0o=polar(cx,cy,52,a0+90),p1o=polar(cx,cy,52,a1+90),p0i=polar(cx,cy,38,a0+90),p1i=polar(cx,cy,38,a1+90); - svgEl(s,'path',{d:`M ${p0i[0]} ${p0i[1]} L ${p0o[0]} ${p0o[1]} A 52 52 0 0 1 ${p1o[0]} ${p1o[1]} L ${p1i[0]} ${p1i[1]} A 38 38 0 0 0 ${p0i[0]} ${p0i[1]} Z`, - fill:CAM_COLS[st],opacity:.75,stroke:'#0a0908','stroke-width':.8}); - const [tx,ty]=polar(cx,cy,45,i*30+15); - svgEl(s,'text',{x:tx,y:ty+2,'text-anchor':'middle','font-size':4.4,'font-weight':700, - 'font-family':'var(--mono)',fill:st==='OFF'?'var(--cream)':'#14110e'}).textContent=st==='OFF'?'OFF':st[0];}); - svgEl(s,'circle',{cx,cy,r:34,fill:'#211e1a',stroke:'#0a0908','stroke-width':1.5}); - CAM_STEPS.forEach((st,i)=>{if(i===0)return;const [lx,ly]=polar(cx,cy,27,i*30+15); - svgEl(s,'text',{x:lx,y:ly+1.8,'text-anchor':'middle','font-size':4.2, - 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent=st;}); - svgEl(s,'circle',{cx,cy,r:17,fill:'#14110e',stroke:'#2c2820','stroke-width':1.5}); - camPtr=svgEl(s,'polygon',{points:`${cx-2.5},${cy} ${cx+2.5},${cy} ${cx},${cy-31}`,fill:'var(--gold-hi)', - filter:'drop-shadow(0 0 3px rgba(var(--glow-lo),.7))'}); - svgEl(s,'circle',{cx,cy,r:5,fill:'#3a3631',stroke:'#060505','stroke-width':1}); - s.style.cursor='pointer'; - s.addEventListener('click',()=>setCam((camPos+1)%12));})(); -function setCam(i){camPos=i; - camPtr.setAttribute('transform',`rotate(${i*30+15},65,65)`); - setRd('R42',camPos===0?'OFF':'STEP '+camPos+' · '+CAM_STEPS[camPos]);} -setCam(0); -if(!matchMedia('(prefers-reduced-motion: reduce)').matches) - setInterval(()=>{if(camPos>0){const n=(camPos+1)%12;setCam(n);if(n===0)setRd('R42','CYCLE DONE · OFF');}},2000); - /* R43 attitude indicator: sky/ground roll+shift behind a fixed miniature aircraft; 2D drag */ (function(){const s=$('attind'),cx=65,cy=65; const defs=svgEl(s,'defs',{}); @@ -2920,125 +2776,6 @@ let dkPos=0,dkCarry=0; setTimeout(()=>{state=target;busy=false;paint();},1500);}); paint();})(); -/* R48 knife switch (side view): the blade hinges at the left post and lands in the right jaw */ -(function(){const s=$('knifesw'); - const defs=svgEl(s,'defs',{}); - const cu=svgEl(defs,'linearGradient',{id:'ksCu',x1:0,y1:0,x2:0,y2:1}); - svgEl(cu,'stop',{offset:'0','stop-color':'#d09a5c'}); - svgEl(cu,'stop',{offset:'1','stop-color':'#8a5a2a'}); - const HX=32,HY=74; // hinge pivot - svgEl(s,'rect',{x:6,y:56,width:118,height:44,rx:6,fill:'#22262a',stroke:'#0c0d0e','stroke-width':1.5}); - const lamp=svgEl(s,'circle',{cx:116,cy:22,r:4.5,fill:'#1e2a12'}); - svgEl(s,'text',{x:116,y:36,'text-anchor':'middle','font-size':4.8,'letter-spacing':'.08em', - 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent='LIVE'; - // hinge post (left) and jaw clip (right), both on the base - svgEl(s,'rect',{x:HX-6,y:HY-6,width:12,height:16,rx:2,fill:'url(#ksCu)',stroke:'#5c3a18','stroke-width':.8}); - svgEl(s,'rect',{x:96,y:HY-9,width:5,height:18,rx:1.5,fill:'url(#ksCu)',stroke:'#5c3a18','stroke-width':.7}); - svgEl(s,'rect',{x:104,y:HY-9,width:5,height:18,rx:1.5,fill:'url(#ksCu)',stroke:'#5c3a18','stroke-width':.7}); - // blade + upright handle, hinged at (HX,HY) - const blade=svgEl(s,'g',{}); - svgEl(blade,'rect',{x:HX,y:HY-3,width:71,height:6,rx:2.5,fill:'url(#ksCu)',stroke:'#5c3a18','stroke-width':.8}); - svgEl(blade,'rect',{x:88,y:HY-26,width:7,height:26,rx:3,fill:'#14110e',stroke:'#000','stroke-width':1}); - svgEl(blade,'circle',{cx:91.5,cy:HY-26,r:5,fill:'#14110e',stroke:'#000','stroke-width':1}); - svgEl(s,'circle',{cx:HX,cy:HY,r:3,fill:'#5c3a18'}); - blade.style.transformBox='view-box';blade.style.transformOrigin=`${HX}px ${HY}px`; - blade.style.transition='transform .28s ease'; - let closed=true; - const draw=()=>{blade.style.transform=closed?'rotate(0deg)':'rotate(-46deg)'; - lamp.setAttribute('fill',closed?'var(--pass)':'#1e2a12'); - lamp.setAttribute('filter',closed?'drop-shadow(0 0 4px rgba(116,147,47,.8))':'none'); - setRd('R48',closed?'CLOSED · live':'OPEN · visibly dead');}; - s.style.cursor='pointer'; - s.addEventListener('click',()=>{closed=!closed;draw();}); - draw();})(); - -/* R49 decade box: four skirted knobs, one digit each; the value is their sum */ -(function(){const s=$('decbox'); - const MUL=[1000,100,10,1],LBL=['x1000','x100','x10','x1']; - const digs=[3,5,0,0]; - svgEl(s,'rect',{x:2,y:2,width:156,height:96,rx:8,fill:'#17140f',stroke:'#060505','stroke-width':1.5}); - const wins=[],knobs=[]; - const total=()=>digs.reduce((a,d,i)=>a+d*MUL[i],0); - const draw=()=>{wins.forEach((w,i)=>w.textContent=String(digs[i])); - knobs.forEach((k,i)=>k.setAttribute('transform',`rotate(${digs[i]*36},${28+i*35},58)`)); - setRd('R49',total().toLocaleString('en-US')+' Ω');}; - MUL.forEach((m,i)=>{const cx=28+i*35,cy=58; - svgEl(s,'rect',{x:cx-8,y:16,width:16,height:12,rx:2,fill:'#0a0806',stroke:'#2c2820','stroke-width':1}); - wins.push(svgEl(s,'text',{x:cx,y:25.5,'text-anchor':'middle','font-size':8,'font-weight':700, - 'font-family':'var(--mono)',fill:'var(--cream)'})); - svgEl(s,'circle',{cx,cy,r:15,fill:'#211e1a',stroke:'#0a0908','stroke-width':1.5}); // skirt - for(let t=0;t<10;t++){const [x1,y1]=polar(cx,cy,14,t*36),[x2,y2]=polar(cx,cy,12,t*36); - svgEl(s,'line',{x1,y1,x2,y2,stroke:'var(--steel)','stroke-width':.6,opacity:.7});} - const k=svgEl(s,'g',{}); - svgEl(k,'circle',{cx,cy,r:10,fill:'#2c2824',stroke:'#060505','stroke-width':1}); - svgEl(k,'line',{x1:cx,y1:cy-9,x2:cx,y2:cy-4,stroke:'var(--gold-hi)','stroke-width':1.8}); - knobs.push(k); - svgEl(s,'text',{x:cx,y:88,'text-anchor':'middle','font-size':5.4,'letter-spacing':'.06em', - 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent=LBL[i]; - const hit=svgEl(s,'circle',{cx,cy,r:16,fill:'transparent'}); - hit.style.cursor='ns-resize'; - dragDelta(hit,()=>digs[i],v=>{digs[i]=Math.round(Math.max(0,Math.min(9,v)));draw();},{min:0,max:9,sens:9/70});}); - draw();})(); - -/* R50 two-hand safety: one palm button arms a 500ms window; the other completes the cycle */ -(function(){const s=$('twohand'); - svgEl(s,'rect',{x:2,y:2,width:156,height:96,rx:8,fill:'#1c1916',stroke:'#060505','stroke-width':1.5}); - const runLamp=svgEl(s,'circle',{cx:80,cy:24,r:5.5,fill:'#1e2a12'}); - svgEl(s,'text',{x:80,y:40,'text-anchor':'middle','font-size':5.4,'letter-spacing':'.12em', - 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent='CYCLE'; - const mkBtn=(cx,lbl)=>{const g=svgEl(s,'g',{}); - svgEl(g,'ellipse',{cx,cy:70,rx:19,ry:6,fill:'#0d0b09'}); - const cap=svgEl(g,'circle',{cx,cy:62,r:16,fill:'#8f2416',stroke:'#5c150c','stroke-width':1.5}); - svgEl(g,'ellipse',{cx:cx-5,cy:56,rx:6,ry:3.4,fill:'rgba(255,255,255,.22)'}); - svgEl(s,'text',{x:cx,y:92,'text-anchor':'middle','font-size':5.4,'letter-spacing':'.08em', - 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent=lbl; - g.style.cursor='pointer';return {g,cap};}; - const L=mkBtn(32,'LEFT PALM'),R=mkBtn(128,'RIGHT PALM'); - let armed=null,armT=null,busy=false; - const flash=(cap)=>{cap.setAttribute('fill','#e0523a');setTimeout(()=>cap.setAttribute('fill','#8f2416'),200);}; - const fault=()=>{armed=null;clearTimeout(armT); - setRd('R50','TIE-DOWN FAULT · release and retry');}; - const press=side=>{if(busy)return; - if(armed===null){armed=side;flash(side==='L'?L.cap:R.cap); - setRd('R50',side+' armed · other hand within 0.5s'); - armT=setTimeout(fault,500);return;} - if(armed===side){fault();return;} // same hand twice = tie-down - clearTimeout(armT);armed=null;busy=true; - flash(side==='L'?L.cap:R.cap); - runLamp.setAttribute('fill','var(--pass)'); - runLamp.setAttribute('filter','drop-shadow(0 0 5px rgba(116,147,47,.8))'); - setRd('R50','CYCLE RUNNING'); - setTimeout(()=>{runLamp.setAttribute('fill','#1e2a12');runLamp.removeAttribute('filter'); - busy=false;setRd('R50','ready');},1500);}; - L.g.addEventListener('click',()=>press('L')); - R.g.addEventListener('click',()=>press('R')); - setRd('R50','ready');})(); - -/* R51 voice-loop keyset: independent monitor states, exclusive talk, activity flicker */ -(function(){const host=$('vloop'); - const LOOPS=['FD','GNC','ECOM','SURG','A/G','NET1','NET2','PAO']; - const keys=LOOPS.map((l,i)=>{const k=document.createElement('div');k.className='vk'; - k.innerHTML=l+'';k.dataset.state='0';host.appendChild(k);return k;}); - // a legible default: FD + A/G monitored, GNC talking - keys[0].dataset.state='1';keys[0].classList.add('mon'); - keys[4].dataset.state='1';keys[4].classList.add('mon'); - keys[1].dataset.state='2';keys[1].classList.add('mon','tlk'); - const refresh=()=>{const mon=keys.filter(k=>k.dataset.state!=='0').length; - const tlk=keys.findIndex(k=>k.dataset.state==='2'); - setRd('R51',mon===0?'all loops dropped':mon+' monitored'+(tlk>=0?' · talk '+LOOPS[tlk]:''));}; - keys.forEach((k,i)=>k.addEventListener('click',()=>{ - const st=k.dataset.state; - if(st==='0'){k.dataset.state='1';k.classList.add('mon');} - else if(st==='1'){keys.forEach(o=>{if(o.dataset.state==='2'){o.dataset.state='1';o.classList.remove('tlk');}}); - k.dataset.state='2';k.classList.add('tlk');} - else{k.dataset.state='0';k.classList.remove('mon','tlk','act');} - refresh();})); - if(!matchMedia('(prefers-reduced-motion: reduce)').matches) - setInterval(()=>{const t=performance.now()/1000; - keys.forEach((k,i)=>{if(k.dataset.state==='0'){k.classList.remove('act');return;} - k.classList.toggle('act',Math.sin(t*(1.1+i*0.37)+i*2.1)>0.55);});},300); - refresh();})(); - /* R52 blinkenlights: live register lamps driven by a pseudo-PC that folds in the switch register */ (function(){const s=$('blinken'); svgEl(s,'rect',{x:2,y:2,width:166,height:96,rx:8,fill:'#3a1420',stroke:'#060505','stroke-width':1.5}); -- cgit v1.2.3