From a95200c188327dc17da23710a65d2d19a3c6b485 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 12 Jul 2026 22:43:53 -0500 Subject: refactor(gallery): extract indicators R35, R36, R45-R47, R52 into GW builders --- docs/prototypes/panel-widget-gallery.html | 188 ++---------------------------- 1 file changed, 12 insertions(+), 176 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 f81b60b..2eae646 100644 --- a/docs/prototypes/panel-widget-gallery.html +++ b/docs/prototypes/panel-widget-gallery.html @@ -1868,22 +1868,22 @@ card(I,'R31','Radar sweep', (st,rd)=>GW.radarSweep(st,{onChange:(v,t)=>rd(t)}), 'the rotating scan. 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)}), 'two coaxial discs, one index. 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)}), 'a bitmap, one red dot at a time. 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)}), 'the mechanical pixel. 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)}), 'a count as a position, and the counting is visible. 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)}), 'three greens or nothing. 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)}), 'the machine thinking, out loud. 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 ============ */ @@ -1994,179 +1994,15 @@ if(!reduced){ document.querySelectorAll('.reel.spin').forEach(r=>r.style.animationPlayState='paused'); } -/* 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; -(function(){const s=$('daydate'),cx=65,cy=65,HAND=135; - const defs=svgEl(s,'defs',{}); - const fc=svgEl(defs,'radialGradient',{id:'ddFace',cx:'46%',cy:'40%',r:'75%'}); - svgEl(fc,'stop',{offset:'0','stop-color':'#efe9da'}); - svgEl(fc,'stop',{offset:'1','stop-color':'#cfc8b6'}); - // movement plate + white calendar disc - svgEl(s,'circle',{cx,cy,r:62,fill:'#17140f',stroke:'#060505','stroke-width':2}); - for(let k=0;k<5;k++){const [x,y]=polar(cx,cy,58,20+k*72); - svgEl(s,'circle',{cx:x,cy:y,r:1.8,fill:'#060505'});} - svgEl(s,'circle',{cx,cy,r:54,fill:'url(#ddFace)',stroke:'#8f897b','stroke-width':.8}); - // date ring: 31 radial numerals on its own group - ddDateG=svgEl(s,'g',{}); - for(let d=1;d<=31;d++){const a=(d-1)*(360/31); - const g=svgEl(ddDateG,'g',{transform:`rotate(${a},${cx},${cy})`}); - svgEl(g,'text',{x:cx,y:cy-46,'text-anchor':'middle','font-size':7.5,'font-weight':700, - 'font-family':'var(--mono)',fill:'#14110e'}).textContent=String(d);} - // inner weekday disc: 7 names twice around, separated by a hairline ring - svgEl(s,'circle',{cx,cy,r:38,fill:'none',stroke:'#8f897b','stroke-width':.7}); - ddDayG=svgEl(s,'g',{}); - for(let i=0;i<14;i++){const a=i*(360/14); - const g=svgEl(ddDayG,'g',{transform:`rotate(${a},${cx},${cy})`}); - svgEl(g,'text',{x:cx,y:cy-28,'text-anchor':'middle','font-size':6.8,'font-weight':700, - 'font-family':'var(--mono)',fill:'#14110e'}).textContent=DD_DAYS[i%7];} - // fixed yellow hand from hub toward the read index, and the hub - const [hx,hy]=polar(cx,cy,44,HAND); - svgEl(s,'line',{x1:cx,y1:cy,x2:hx,y2:hy,stroke:'#e8cf4a','stroke-width':3,'stroke-linecap':'round'}); - svgEl(s,'line',{x1:cx,y1:cy,x2:hx,y2:hy,stroke:'rgba(0,0,0,.25)','stroke-width':.8}); - svgEl(s,'circle',{cx,cy,r:6,fill:'#b8b2a4',stroke:'#5c574c','stroke-width':1.2}); - svgEl(s,'circle',{cx,cy,r:2.4,fill:'#7a7466'}); - s.style.cursor='pointer'; - s.addEventListener('click',()=>{setDdcal(ddDate%31+1,(ddDay+1)%7);});})(); -function setDdcal(d,w){ddDate=d;ddDay=w;const HAND=135; - ddDateG.setAttribute('transform',`rotate(${(HAND-(d-1)*(360/31)).toFixed(2)},65,65)`); - ddDayG.setAttribute('transform',`rotate(${(HAND-w*(360/14)).toFixed(2)},65,65)`); - setRd('R35',DD_DAYS[w]+' '+d);} -(function(){const now=new Date();setDdcal(now.getDate(),now.getDay());})(); - -/* R36 LED dot matrix: 8x8 paintable bitmap, starts on the reference K */ -(function(){const s=$('dotmx'),N=8,STEP=11,X0=10.5,Y0=10.5; - const K8=['10000100','10001000','10010000','11100000','10100000','10010000','10001000','10000100']; - svgEl(s,'rect',{x:2,y:2,width:104,height:104,rx:8,fill:'#17140f',stroke:'#060505','stroke-width':1.5}); - svgEl(s,'rect',{x:6,y:6,width:96,height:96,rx:5,fill:'#0d0505',stroke:'#241010','stroke-width':1}); - const dots=[];let lit=0; - const paint=(d,on)=>{d.on=on; - d.el.setAttribute('fill',on?'#ff4a30':'#2a1210'); - d.el.setAttribute('filter',on?'drop-shadow(0 0 3.5px rgba(255,74,48,.85))':'none');}; - for(let r=0;r{paint(d,!d.on);lit+=d.on?1:-1; - setRd('R36',lit+' / 64 lit');});} - setRd('R36',lit+' / 64 lit');})(); +/* R35, R36 extracted to widgets.js (GW.*) */ + /* R43, R44 extracted to widgets.js (GW.*) */ -/* R45 flip-disc: bistable mechanical pixels; scaleX flip with color swap at the midpoint */ -(function(){const s=$('flipdisc'),COLS=7,ROWS=5,STEP=14,X0=13,Y0=13; - const GLYPH=['0000100','0000010','1111111','0000010','0000100']; // arrow right - svgEl(s,'rect',{x:2,y:2,width:116,height:88,rx:7,fill:'#17140f',stroke:'#060505','stroke-width':1.5}); - let lit=0; - const discs=[]; - for(let r=0;r{d.on=!d.on;lit+=d.on?1:-1; - if(matchMedia('(prefers-reduced-motion: reduce)').matches){el.setAttribute('fill',d.on?'#e3d44f':'#141210');} - else{el.style.transform='scaleX(0)'; - setTimeout(()=>{el.setAttribute('fill',d.on?'#e3d44f':'#141210');el.style.transform='scaleX(1)';},115);} - setRd('R45',lit+' discs showing');}); - discs.push(d);} - setRd('R45',lit+' discs showing');})(); - -/* R46 dekatron: click pulses the neon glow one cathode around; wrap = carry */ -let dkPos=0,dkCarry=0; -(function(){const s=$('dekatron'),cx=60,cy=60; - svgEl(s,'circle',{cx,cy,r:52,fill:'#171310',stroke:'#060505','stroke-width':2}); - svgEl(s,'circle',{cx,cy,r:46,fill:'#0d0a08',stroke:'#2a221c','stroke-width':1}); - svgEl(s,'ellipse',{cx:cx-14,cy:cy-18,rx:20,ry:11,fill:'rgba(255,255,255,.05)',transform:`rotate(-24,${cx-14},${cy-18})`}); - const dots=[]; - for(let i=0;i<10;i++){const [x,y]=polar(cx,cy,34,i*36); - svgEl(s,'text',{x:polar(cx,cy,43,i*36)[0],y:polar(cx,cy,43,i*36)[1]+2,'text-anchor':'middle','font-size':5.2, - 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent=String(i); - dots.push(svgEl(s,'circle',{cx:x,cy:y,r:3.4,fill:'#3a2a20'}));} - const carry=svgEl(s,'circle',{cx,cy:cy-6,r:3,fill:'#2a1c14'}); - svgEl(s,'text',{x:cx,y:cy+8,'text-anchor':'middle','font-size':4.6,'letter-spacing':'.12em', - 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent='CARRY'; - const draw=()=>{dots.forEach((d,i)=>{const on=i===dkPos; - d.setAttribute('fill',on?'#ff9a4c':'#3a2a20'); - d.setAttribute('filter',on?'drop-shadow(0 0 5px rgba(255,154,76,.9))':'none');}); - setRd('R46','COUNT '+dkPos+(dkCarry?' · CARRY x'+dkCarry:''));}; - s.style.cursor='pointer'; - s.addEventListener('click',()=>{dkPos=(dkPos+1)%10; - if(dkPos===0){dkCarry++;carry.setAttribute('fill','#ff9a4c'); - carry.setAttribute('filter','drop-shadow(0 0 4px rgba(255,154,76,.9))'); - setTimeout(()=>{carry.setAttribute('fill','#2a1c14');carry.removeAttribute('filter');},220);} - draw();}); - draw();})(); - -/* R47 landing gear: three greens or nothing; lever cycles down -> transit -> up */ -(function(){const s=$('gearind'); - svgEl(s,'rect',{x:2,y:2,width:136,height:106,rx:9,fill:'#1c1916',stroke:'#060505','stroke-width':1.5}); - const POS=[['NOSE',48,28],['L',24,74],['R',72,74]]; - const lamps={}; - POS.forEach(([n,x,y])=>{ - svgEl(s,'circle',{cx:x,cy:y,r:9,fill:'#0d0b09',stroke:'#2c2820','stroke-width':1.5}); - lamps[n]=svgEl(s,'circle',{cx:x,cy:y,r:6.2,fill:'#16240f'}); - svgEl(s,'text',{x,y:y+18,'text-anchor':'middle','font-size':5.2,'letter-spacing':'.08em', - 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent=n;}); - // gear lever: slot + wheel-shaped knob - svgEl(s,'rect',{x:106,y:22,width:8,height:66,rx:4,fill:'#0d0b09',stroke:'#2c2820','stroke-width':1}); - const lever=svgEl(s,'g',{}); - svgEl(lever,'rect',{x:107.5,y:36,width:5,height:26,fill:'#8f8a7e'}); - svgEl(lever,'circle',{cx:110,cy:32,r:8,fill:'#26221c',stroke:'#4a463e','stroke-width':2.4}); - svgEl(lever,'circle',{cx:110,cy:32,r:3,fill:'#4a463e'}); - svgEl(s,'text',{x:110,y:100,'text-anchor':'middle','font-size':5.2,'letter-spacing':'.08em', - 'font-family':'var(--mono)',fill:'var(--steel)'}).textContent='GEAR'; - let state='down',busy=false; - const paint=()=>{const c=state==='down'?'var(--pass)':state==='transit'?'var(--amber-warn)':'#16240f'; - for(const n of Object.keys(lamps)){lamps[n].setAttribute('fill',c); - lamps[n].setAttribute('filter',state==='down'?'drop-shadow(0 0 4px rgba(116,147,47,.8))': - state==='transit'?'drop-shadow(0 0 4px rgba(var(--glow-lo),.7))':'none'); - lamps[n].style.animation=state==='transit'?'pulse var(--pulse-rate) ease-in-out infinite':'none';} - lever.setAttribute('transform',state==='up'?'translate(0,-8)':state==='transit'?'translate(0,-4)':''); - setRd('R47',state==='down'?'GEAR DOWN · 3 GREEN':state==='up'?'GEAR UP':'IN TRANSIT');}; - s.style.cursor='pointer'; - s.addEventListener('click',()=>{if(busy)return; - const target=state==='down'?'up':'down'; - if(matchMedia('(prefers-reduced-motion: reduce)').matches){state=target;paint();return;} - busy=true;state='transit';paint(); - setTimeout(()=>{state=target;busy=false;paint();},1500);}); - paint();})(); - -/* 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}); - svgEl(s,'rect',{x:8,y:8,width:154,height:62,rx:5,fill:'#14090e'}); - const mkRow=(y,lbl)=>{const row=[]; - svgEl(s,'text',{x:12,y:y+2.6,'font-size':4.6,'letter-spacing':'.08em','font-family':'var(--mono)', - fill:'#b08a96'}).textContent=lbl; - for(let i=0;i<12;i++)row.push(svgEl(s,'circle',{cx:46+i*10,cy:y,r:3,fill:'#2a1210'})); - return row;}; - const addr=mkRow(24,'ADDR'),data=mkRow(46,'DATA'); - const paint=(row,val)=>row.forEach((d,i)=>{const on=(val>>(11-i))&1; - d.setAttribute('fill',on?'#ffb43a':'#2a1210'); - d.setAttribute('filter',on?'drop-shadow(0 0 3px rgba(255,180,58,.8))':'none');}); - // switch register: 8 mini toggles - let sr=0b10100101; - const toggles=[]; - for(let i=0;i<8;i++){const x=46+i*14; - svgEl(s,'rect',{x:x-3,y:76,width:6,height:16,rx:3,fill:'#0d0b09',stroke:'#4a2432','stroke-width':1}); - const t=svgEl(s,'circle',{cx:x,cy:80,r:4,fill:'#8f8a7e',stroke:'#3c382f','stroke-width':1}); - t.style.cursor='pointer'; - const hit=svgEl(s,'rect',{x:x-7,y:72,width:14,height:24,fill:'transparent'}); - hit.style.cursor='pointer'; - const flip=()=>{sr^=(1<<(7-i));draw();}; - hit.addEventListener('click',flip); - toggles.push(t);} - svgEl(s,'text',{x:12,y:83,'font-size':4.6,'letter-spacing':'.08em','font-family':'var(--mono)', - fill:'#b08a96'}).textContent='SR'; - let pc=0o1234; - const draw=()=>{toggles.forEach((t,i)=>t.setAttribute('cy',((sr>>(7-i))&1)?76:84)); - setRd('R52','SR '+sr.toString(8).padStart(3,'0')+' (octal)');}; - const tick=()=>{pc=((pc*5+sr+1)&0xFFF);paint(addr,pc);paint(data,(pc^(pc<<3)^sr)&0xFFF);}; - draw();tick(); - if(!matchMedia('(prefers-reduced-motion: reduce)').matches)setInterval(tick,250);})(); +/* R45-R47, R52 extracted to widgets.js (GW.*) */ + + + /* R53-R56 extracted to widgets.js (GW.*) */ -- cgit v1.2.3