aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes/panel-widget-gallery.html
diff options
context:
space:
mode:
Diffstat (limited to 'docs/prototypes/panel-widget-gallery.html')
-rw-r--r--docs/prototypes/panel-widget-gallery.html283
1 files changed, 21 insertions, 262 deletions
diff --git a/docs/prototypes/panel-widget-gallery.html b/docs/prototypes/panel-widget-gallery.html
index 947fd16..b192fb0 100644
--- a/docs/prototypes/panel-widget-gallery.html
+++ b/docs/prototypes/panel-widget-gallery.html
@@ -1614,28 +1614,28 @@ 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',
- `<svg id="vern" class="rsvg drag" viewBox="0 0 150 150" width="150" height="150"></svg>`,
+ (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',
- `<svg id="batsw" class="rsvg press" viewBox="0 0 70 90" width="70" height="90"></svg>`,
+ (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',
- `<svg id="bakek" class="rsvg drag" viewBox="0 0 110 110" width="110" height="110"></svg>`,
+ (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',
- `<svg id="fbank" class="rsvg" viewBox="0 0 190 96" width="190" height="96"></svg>`,
+ (st,rd)=>GW.filterBank(st,{onChange:(v,t)=>rd(t)}),
'<b>a wall of band faders.</b> One slider per band, arrow-head caps on black tracks, dB rail at the side. Drag any cap; readout names band and level. After a variable multi-band filter.');
card(C,'R06','Chicken-head selector',
- `<svg id="chick" class="rsvg press" viewBox="0 0 110 96" width="110" height="96"></svg>`,
+ (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',
- `<svg id="slotf" class="rsvg drag" viewBox="0 0 90 150" width="90" height="150"></svg>`,
+ (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',
- `<svg id="spade" class="rsvg drag" viewBox="0 0 140 124" width="140" height="124"></svg>`,
+ (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',
- `<svg id="mband" class="rsvg" viewBox="0 0 190 110" width="190" height="110"></svg>`,
+ (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',
`<svg id="keypad" class="rsvg" viewBox="0 0 150 190" width="150" height="190"></svg>`,
@@ -2079,147 +2079,15 @@ function setMcVu(t){t=Math.max(0,Math.min(1.02,t));mcvu.t=t;
function tickMcVu(){const a=lvl();const target=Math.min(1.02,a*1.12);
setMcVu(mcvu.t+(target-mcvu.t)*(target>mcvu.t?0.32:0.10));}
-/* R02 calibrated vernier dial: the disc turns under a fixed hairline */
-let vernV=42.0,vernDisc=null;
-(function(){const s=$('vern'),cx=75,cy=75;
- const defs=svgEl(s,'defs',{});
- const fg=svgEl(defs,'radialGradient',{id:'vernFace',cx:'50%',cy:'45%',r:'60%'});
- svgEl(fg,'stop',{offset:'0','stop-color':'#f7f2da'});
- svgEl(fg,'stop',{offset:'1','stop-color':'#e4dfc2'});
- const bk=svgEl(defs,'radialGradient',{id:'bakeKnob',cx:'42%',cy:'36%',r:'75%'});
- svgEl(bk,'stop',{offset:'0','stop-color':'#37332c'});
- svgEl(bk,'stop',{offset:'1','stop-color':'#060505'});
- svgEl(s,'circle',{cx,cy,r:72,fill:'#0c0b0a',stroke:'#262320'});
- vernDisc=svgEl(s,'g',{});
- svgEl(vernDisc,'circle',{cx,cy,r:66,fill:'url(#vernFace)',stroke:'#b7b29a','stroke-width':1});
- for(let u=0;u<=100;u++){const a=u*3,major=u%10===0,half=u%5===0;
- const len=major?10:half?7:4.5,[x1,y1]=polar(cx,cy,63,a),[x2,y2]=polar(cx,cy,63-len,a);
- svgEl(vernDisc,'line',{x1,y1,x2,y2,stroke:'#3a3128','stroke-width':major?1.3:half?1:.7});
- if(major){const g=svgEl(vernDisc,'g',{transform:`rotate(${a},${cx},${cy})`});
- svgEl(g,'text',{x:cx,y:cy-44,'text-anchor':'middle','font-size':7.5,'font-family':'var(--mono)',
- fill:'#3a3128'}).textContent=u;}}
- for(let k=0;k<12;k++){const [x,y]=polar(cx,cy,24,k*30);
- svgEl(s,'circle',{cx:x,cy:y,r:4.8,fill:'url(#bakeKnob)'});}
- svgEl(s,'circle',{cx,cy,r:24,fill:'url(#bakeKnob)',stroke:'#000','stroke-width':.6});
- svgEl(s,'circle',{cx,cy,r:16,fill:'url(#bakeKnob)'});
- svgEl(s,'ellipse',{cx:69,cy:66,rx:7,ry:4,fill:'rgba(255,255,255,.10)',transform:'rotate(-32,69,66)'});
- svgEl(s,'line',{x1:cx,y1:6,x2:cx,y2:22,stroke:'var(--fail)','stroke-width':2.2,'stroke-linecap':'round'});})();
-function setVern(v){vernV=v;vernDisc.setAttribute('transform',`rotate(${-v*3},75,75)`);setRd('R02',v.toFixed(1));}
-dragDelta($('vern'),()=>vernV,setVern,{min:0,max:100,sens:0.15});setVern(42.0);
-
-/* R03 bat-handle toggle */
-let batOn=true,batLever=null,batLblOn=null,batLblOff=null;
-(function(){const s=$('batsw'),cx=35,cy=44;
- const defs=svgEl(s,'defs',{});
- const ng=svgEl(defs,'linearGradient',{id:'nutG',x1:0,y1:0,x2:0,y2:1});
- svgEl(ng,'stop',{offset:'0','stop-color':'#8a8578'});
- svgEl(ng,'stop',{offset:'1','stop-color':'#4e4a42'});
- const cg=svgEl(defs,'linearGradient',{id:'chromeG',x1:0,y1:0,x2:1,y2:0});
- svgEl(cg,'stop',{offset:'0','stop-color':'#7e7a70'});
- svgEl(cg,'stop',{offset:'.45','stop-color':'#e8e5db'});
- svgEl(cg,'stop',{offset:'1','stop-color':'#8a867c'});
- const tg=svgEl(defs,'radialGradient',{id:'tipG',cx:'40%',cy:'32%',r:'75%'});
- svgEl(tg,'stop',{offset:'0','stop-color':'#f2efe8'});
- svgEl(tg,'stop',{offset:'1','stop-color':'#7e7a70'});
- batLblOn=svgEl(s,'text',{x:cx,y:9,'text-anchor':'middle','font-size':7,'letter-spacing':'.1em',
- 'font-family':'var(--mono)',fill:'var(--gold-hi)'});batLblOn.textContent='ON';
- batLblOff=svgEl(s,'text',{x:cx,y:87,'text-anchor':'middle','font-size':7,'letter-spacing':'.1em',
- 'font-family':'var(--mono)',fill:'var(--dim)'});batLblOff.textContent='OFF';
- const pts=[];for(let k=0;k<6;k++){const a=(k*60+30)*Math.PI/180;pts.push((cx+17*Math.cos(a))+','+(cy+17*Math.sin(a)));}
- svgEl(s,'polygon',{points:pts.join(' '),fill:'url(#nutG)',stroke:'#2b2822'});
- svgEl(s,'circle',{cx,cy,r:9,fill:'#14110e',stroke:'#000'});
- batLever=svgEl(s,'g',{});
- svgEl(batLever,'path',{d:`M ${cx-3.2} ${cy} L ${cx-1.8} 21 L ${cx+1.8} 21 L ${cx+3.2} ${cy} Z`,fill:'url(#chromeG)',stroke:'#4e4a42','stroke-width':.5});
- svgEl(batLever,'ellipse',{cx,cy:17,rx:6.5,ry:8,fill:'url(#tipG)',stroke:'#5e5a52','stroke-width':.6});
- batLever.style.transformOrigin=`${cx}px ${cy}px`;batLever.style.transition='transform .12s';
- $('batsw').onclick=()=>setBat(!batOn);})();
-function setBat(on){batOn=on;batLever.style.transform=on?'rotate(0deg)':'rotate(180deg)';
- batLblOn.setAttribute('fill',on?'var(--gold-hi)':'var(--dim)');
- batLblOff.setAttribute('fill',on?'var(--dim)':'var(--gold-hi)');setRd('R03',on?'ON':'OFF');}
-setBat(true);
-
-/* R04 bakelite fluted knob over a printed 0-10 scale */
-let bakeV=63,bakeIdx=null;
-(function(){const s=$('bakek'),cx=55,cy=54;
- const defs=svgEl(s,'defs',{});
- const sk=svgEl(defs,'radialGradient',{id:'bakeSk',cx:'42%',cy:'36%',r:'80%'});
- svgEl(sk,'stop',{offset:'0','stop-color':'#312d27'});
- svgEl(sk,'stop',{offset:'1','stop-color':'#050404'});
- const dm=svgEl(defs,'radialGradient',{id:'bakeDome',cx:'40%',cy:'32%',r:'80%'});
- svgEl(dm,'stop',{offset:'0','stop-color':'#3a362f'});
- svgEl(dm,'stop',{offset:'1','stop-color':'#0a0908'});
- for(let i=0;i<=10;i++){const a=-135+i*27,major=i%5===0;
- const [x1,y1]=polar(cx,cy,38,a),[x2,y2]=polar(cx,cy,major?45:43.5,a);
- svgEl(s,'line',{x1,y1,x2,y2,stroke:'var(--steel)','stroke-width':major?1.5:1});
- if(major){const [x,y]=polar(cx,cy,50.5,a);
- svgEl(s,'text',{x,y:y+2.6,'text-anchor':'middle','font-size':8,'font-family':'var(--mono)',
- fill:'var(--steel)'}).textContent=i;}}
- for(let k=0;k<18;k++){const [x,y]=polar(cx,cy,31,k*20);
- svgEl(s,'circle',{cx:x,cy:y,r:3.4,fill:'url(#bakeSk)'});}
- svgEl(s,'circle',{cx,cy,r:31,fill:'url(#bakeSk)',stroke:'#000','stroke-width':.6});
- bakeIdx=svgEl(s,'line',{x1:cx,y1:cy-30,x2:cx,y2:cy-21,stroke:'var(--gold-hi)','stroke-width':2.4,
- 'stroke-linecap':'round'});
- svgEl(s,'circle',{cx,cy,r:19,fill:'url(#bakeDome)'});
- svgEl(s,'ellipse',{cx:49,cy:46,rx:6,ry:3.5,fill:'rgba(255,255,255,.14)',transform:'rotate(-28,49,46)'});})();
-function setBake(v){bakeV=v;bakeIdx.setAttribute('transform',`rotate(${v*2.7-135},55,54)`);
- setRd('R04',(v/10).toFixed(1)+' / 10');}
-dragDelta($('bakek'),()=>bakeV,setBake,{min:0,max:100,sens:0.25});setBake(63);
-
-/* vuDb (VU law) lives in widgets.js (GW) */
-
-/* R05 filter slider bank: one fader per band, arrow caps on black tracks */
-const FB_FREQ=[78,136,235,406,701,1210,2090,3620];
-const fbank={vals:[18,30,42,25,35,55,20,48],caps:[]};
-(function(){const s=$('fbank'),y0=14,y1=84,x0=30,dx=19.5;
- const yOf=v=>y0+v/60*(y1-y0);
- for(const [lbl,y] of [['0',y0],['20',y0+(y1-y0)/3],['40',y0+2*(y1-y0)/3],['60',y1]]){
- svgEl(s,'text',{x:14,y:y+2,'text-anchor':'end','font-size':6,'font-family':'var(--mono)',
- fill:'var(--steel)'}).textContent=lbl;}
- svgEl(s,'text',{x:14,y:94,'text-anchor':'end','font-size':5.5,'font-family':'var(--mono)',
- fill:'var(--dim)'}).textContent='dB';
- FB_FREQ.forEach((f,i)=>{const x=x0+i*dx;
- svgEl(s,'text',{x,y:8,'text-anchor':'middle','font-size':5.2,'font-family':'var(--mono)',
- fill:'var(--steel)'}).textContent=f<1000?f:(f/1000)+'k';
- svgEl(s,'line',{x1:x,y1:y0,x2:x,y2:y1,stroke:'#0a0908','stroke-width':3.4,'stroke-linecap':'round'});
- svgEl(s,'line',{x1:x-.7,y1:y0,x2:x-.7,y2:y1,stroke:'rgba(255,255,255,.05)','stroke-width':.7});
- const cap=svgEl(s,'g',{});
- svgEl(cap,'polygon',{points:`${x-8.5},${-4.5} ${x-2.5},0 ${x-8.5},${4.5}`,fill:'var(--vfd)',stroke:'#123028','stroke-width':.6});
- svgEl(cap,'polygon',{points:`${x+8.5},${-4.5} ${x+2.5},0 ${x+8.5},${4.5}`,fill:'var(--vfd)',stroke:'#123028','stroke-width':.6});
- svgEl(cap,'circle',{cx:x,cy:0,r:1.5,fill:'#123028'});
- fbank.caps.push(cap);
- const hit=svgEl(s,'rect',{x:x-9.5,y:y0-6,width:19,height:y1-y0+12,fill:'transparent'});
- hit.style.cursor='ns-resize';
- dragY(hit,pct=>setFbank(i,(100-pct)/100*60));});
- })();
-function setFbank(i,db){db=Math.max(0,Math.min(60,db));fbank.vals[i]=db;
- fbank.caps[i].setAttribute('transform',`translate(0,${14+db/60*70})`);
- setRd('R05',`${FB_FREQ[i]<1000?FB_FREQ[i]:(FB_FREQ[i]/1000)+'k'} Hz · −${Math.round(db)} dB`);}
-fbank.vals.forEach((v,i)=>setFbank(i,v));setRd('R05','drag a band');
-
-/* R06 chicken-head selector: the tapered lever aims at the position */
-const CHICK_POS=[['OFF',-60],['LO',-20],['MID',20],['HI',60]];
-let chickI=2,chickLever=null,chickLbls=[];
-(function(){const s=$('chick'),cx=55,cy=58;
- const defs=svgEl(s,'defs',{});
- const bg=svgEl(defs,'radialGradient',{id:'chickG',cx:'42%',cy:'34%',r:'80%'});
- svgEl(bg,'stop',{offset:'0','stop-color':'#35312b'});
- svgEl(bg,'stop',{offset:'1','stop-color':'#070606'});
- CHICK_POS.forEach(([lbl,a],i)=>{const [tx,ty]=polar(cx,cy,42,a);
- const t=svgEl(s,'text',{x:tx,y:ty+2.5,'text-anchor':'middle','font-size':6.5,'letter-spacing':'.06em',
- 'font-family':'var(--mono)',fill:'var(--dim)'});t.textContent=lbl;chickLbls.push(t);
- const [mx1,my1]=polar(cx,cy,33,a),[mx2,my2]=polar(cx,cy,29,a);
- svgEl(s,'line',{x1:mx1,y1:my1,x2:mx2,y2:my2,stroke:'var(--steel)','stroke-width':1.2});});
- svgEl(s,'circle',{cx,cy,r:12,fill:'#0c0b0a'});
- chickLever=svgEl(s,'g',{});
- svgEl(chickLever,'path',{d:`M ${cx} ${cy-29} L ${cx+8.5} ${cy-4} Q ${cx+9} ${cy+8} ${cx+5} ${cy+11} A 7 7 0 0 1 ${cx-5} ${cy+11} Q ${cx-9} ${cy+8} ${cx-8.5} ${cy-4} Z`,
- fill:'url(#chickG)',stroke:'#000','stroke-width':.7});
- svgEl(chickLever,'line',{x1:cx,y1:cy-26,x2:cx,y2:cy+6,stroke:'#4a463e','stroke-width':1.4,'stroke-linecap':'round'});
- chickLever.style.transformOrigin=`${cx}px ${cy}px`;chickLever.style.transition='transform .12s';
- $('chick').onclick=()=>setChick((chickI+1)%CHICK_POS.length);})();
-function setChick(i){chickI=i;chickLever.style.transform=`rotate(${CHICK_POS[i][1]}deg)`;
- chickLbls.forEach((t,k)=>t.setAttribute('fill',k===i?'var(--gold-hi)':'var(--dim)'));
- setRd('R06',CHICK_POS[i][0]);}
-setChick(2);
+/* R02-R06, R12, R14, R15 extracted to widgets.js (GW.*) */
+
+
+
+
+
+
+
+
/* R07 round panel meter: porthole bezel, same VU law as R01 */
let rndT=.5,rndNeedle=null;
@@ -2368,42 +2236,7 @@ function setFlag(on){flagOn=on;flagStripes.style.transform=on?'translateX(0)':'t
setRd('R11',on?'FLAG':'clear');}
setFlag(false);
-/* R12 chrome slot fader: engraved dB scale, chrome T-handle */
-let slotDb=-4,slotHandle=null;
-(function(){const s=$('slotf'),cx=45,yTop=30,yBot=120;
- const yOf=db=>yTop+(12-db)/36*(yBot-yTop);
- const defs=svgEl(s,'defs',{});
- const pl=svgEl(defs,'linearGradient',{id:'sfPlate',x1:0,y1:0,x2:1,y2:1});
- svgEl(pl,'stop',{offset:'0','stop-color':'#242019'});
- svgEl(pl,'stop',{offset:'1','stop-color':'#131110'});
- const ch=svgEl(defs,'linearGradient',{id:'sfChrome',x1:0,y1:0,x2:0,y2:1});
- svgEl(ch,'stop',{offset:'0','stop-color':'#f0efec'});
- svgEl(ch,'stop',{offset:'.45','stop-color':'#c4c1b9'});
- svgEl(ch,'stop',{offset:'1','stop-color':'#75726a'});
- const sc=svgEl(defs,'radialGradient',{id:'sfScrew',cx:'40%',cy:'35%',r:'75%'});
- svgEl(sc,'stop',{offset:'0','stop-color':'#9b968a'});
- svgEl(sc,'stop',{offset:'1','stop-color':'#4a463e'});
- svgEl(s,'rect',{x:14,y:8,width:62,height:134,rx:6,fill:'url(#sfPlate)',stroke:'#0a0908','stroke-width':1.5});
- svgEl(s,'rect',{x:15.5,y:9.5,width:59,height:131,rx:5,fill:'none',stroke:'rgba(255,255,255,.05)','stroke-width':1});
- for(const sy of [16,134]){
- svgEl(s,'circle',{cx,cy:sy,r:3.2,fill:'url(#sfScrew)',stroke:'#14110e','stroke-width':.6});
- const [x1,y1]=polar(cx,sy,3,112),[x2,y2]=polar(cx,sy,3,292);
- svgEl(s,'line',{x1,y1,x2,y2,stroke:'#221f1a','stroke-width':1});}
- svgEl(s,'rect',{x:40.5,y:yTop-4,width:9,height:yBot-yTop+8,rx:4,fill:'#0b0a09',stroke:'#050404'});
- for(const db of [12,8,4,0,-4,-8,-12,-16,-20,-24]){const y=yOf(db);
- svgEl(s,'line',{x1:26,y1:y,x2:39,y2:y,stroke:'var(--steel)','stroke-width':db===0?1.3:.8,opacity:db===0?1:.75});
- svgEl(s,'line',{x1:51,y1:y,x2:64,y2:y,stroke:'var(--steel)','stroke-width':db===0?1.3:.8,opacity:db===0?1:.75});
- svgEl(s,'text',{x:23,y:y+2.2,'text-anchor':'end','font-size':6,'font-family':'var(--mono)',
- fill:db===0?'var(--cream)':'var(--steel)'}).textContent=Math.abs(db);}
- slotHandle=svgEl(s,'g',{});
- svgEl(slotHandle,'polygon',{points:'45,0 58,-5 58,5',fill:'#b9b6ae',stroke:'#5e5a52','stroke-width':.5});
- svgEl(slotHandle,'rect',{x:58,y:-7,width:22,height:14,rx:3.5,fill:'url(#sfChrome)',stroke:'#4e4a42','stroke-width':.6});
- svgEl(slotHandle,'rect',{x:60,y:-5.2,width:18,height:2.2,rx:1,fill:'rgba(255,255,255,.4)'});
- dragY(s,pct=>setSlot(-24+pct/100*36));})();
-function setSlot(db){slotDb=Math.max(-24,Math.min(12,db));
- slotHandle.setAttribute('transform',`translate(0,${30+(12-slotDb)/36*90})`);
- setRd('R12',(slotDb>0?'+':slotDb<0?'−':'')+Math.abs(slotDb).toFixed(1)+' dB');}
-setSlot(-4);
+
/* R13 edgewise strip meter: compressed log scale, bar rides the edge */
const EDGE_SCALE=[[0,.08],[3,.26],[6,.44],[12,.62],[20,.78],[40,.94]];
@@ -2433,83 +2266,9 @@ function setEdge(f){edgeFrac=Math.max(.08,Math.min(.94,f));
setRd('R13','−'+db.toFixed(1)+' dB');}
setEdge(.62);
-/* R14 spade-pointer tuning knob: engraved panel arc, pointer rides the knob */
-let spadeV=8.3,spadeGrp=null;
-(function(){const s=$('spade'),cx=70,cy=82,sweep=v=>-80+v/10*160;
- const defs=svgEl(s,'defs',{});
- const bk=svgEl(defs,'radialGradient',{id:'spadeKnob',cx:'42%',cy:'34%',r:'80%'});
- svgEl(bk,'stop',{offset:'0','stop-color':'#33302a'});
- svgEl(bk,'stop',{offset:'1','stop-color':'#080706'});
- const mt=svgEl(defs,'linearGradient',{id:'spadeMetal',x1:0,y1:0,x2:1,y2:1});
- svgEl(mt,'stop',{offset:'0','stop-color':'#e9e7e0'});
- svgEl(mt,'stop',{offset:'1','stop-color':'#8d897f'});
- // engraved arc: every stroke carries a faint light twin offset below (incised relief)
- const engrave=(x1,y1,x2,y2,w)=>{
- svgEl(s,'line',{x1,y1:y1+.8,x2,y2:y2+.8,stroke:'rgba(255,255,255,.13)','stroke-width':w});
- svgEl(s,'line',{x1,y1,x2,y2,stroke:'#050404','stroke-width':w});};
- for(let i=0;i<=40;i++){const v=i/4,a=sweep(v),major=i%4===0;
- const [x1,y1]=polar(cx,cy,major?50:52,a),[x2,y2]=polar(cx,cy,58,a);
- engrave(x1,y1,x2,y2,major?1.6:.9);
- if(major){const [nx,ny]=polar(cx,cy,65,a);
- svgEl(s,'text',{x:nx,y:ny+3.2,'text-anchor':'middle','font-size':7.5,'font-family':'var(--mono)',
- fill:'rgba(255,255,255,.13)'}).textContent=v;
- svgEl(s,'text',{x:nx,y:ny+2.4,'text-anchor':'middle','font-size':7.5,'font-family':'var(--mono)',
- fill:'#0a0908'}).textContent=v;}}
- spadeGrp=svgEl(s,'g',{});
- svgEl(spadeGrp,'path',{d:`M ${cx-2} ${cy-28} L ${cx-4.5} ${cy-42} L ${cx} ${cy-54} L ${cx+4.5} ${cy-42} L ${cx+2} ${cy-28} Z`,
- fill:'url(#spadeMetal)',stroke:'#55524a','stroke-width':.6});
- svgEl(spadeGrp,'circle',{cx,cy,r:30,fill:'url(#spadeKnob)',stroke:'#000','stroke-width':.8});
- svgEl(spadeGrp,'circle',{cx,cy,r:28.5,fill:'none',stroke:'#0a0908','stroke-width':3,'stroke-dasharray':'2.2 2.2'});
- svgEl(spadeGrp,'circle',{cx,cy,r:22,fill:'url(#spadeKnob)'});
- svgEl(spadeGrp,'ellipse',{cx:cx-8,cy:cy-9,rx:8,ry:5,fill:'rgba(255,255,255,.08)',transform:`rotate(-30,${cx-8},${cy-9})`});})();
-function setSpade(v){spadeV=Math.max(0,Math.min(10,v));
- spadeGrp.setAttribute('transform',`rotate(${-80+spadeV/10*160},70,82)`);
- setRd('R14',spadeV.toFixed(1));}
-dragDelta($('spade'),()=>spadeV,setSpade,{min:0,max:10,sens:.05});setSpade(8.3);
-
-/* R15 multi-band dial: nested arcs, one needle; bandspread selects the ring */
-const MBAND_RANGES=[[0.54,1.6],[1.6,5.1],[5.1,15.5],[15.5,30.5]];
-let mbandV=45,mbandB=2,mbNeedle=null,mbRings=[],mbSpread=null;
-(function(){const s=$('mband'),cx=62,cy=62,sweep=t=>-70+t*140;
- const defs=svgEl(s,'defs',{});
- const pf=svgEl(defs,'linearGradient',{id:'mbFace',x1:0,y1:0,x2:1,y2:1});
- svgEl(pf,'stop',{offset:'0','stop-color':'#eee5c8'});
- svgEl(pf,'stop',{offset:'1','stop-color':'#dcd2ae'});
- svgEl(s,'rect',{x:4,y:4,width:182,height:102,rx:12,fill:'#171412',stroke:'#060505'});
- svgEl(s,'rect',{x:9,y:9,width:172,height:92,rx:9,fill:'url(#mbFace)',stroke:'#0a0908'});
- const INK='#2c2318';
- [18,26,34,42].forEach((r,bi)=>{const ring=svgEl(s,'g',{});
- const [x1,y1]=polar(cx,cy,r,-70),[x2,y2]=polar(cx,cy,r,70);
- svgEl(ring,'path',{d:`M ${x1} ${y1} A ${r} ${r} 0 0 1 ${x2} ${y2}`,fill:'none',stroke:INK,'stroke-width':.9});
- for(let i=0;i<=8;i++){const a=sweep(i/8),[tx1,ty1]=polar(cx,cy,r,a),[tx2,ty2]=polar(cx,cy,r+3,a);
- svgEl(ring,'line',{x1:tx1,y1:ty1,x2:tx2,y2:ty2,stroke:INK,'stroke-width':.8});}
- mbRings.push(ring);});
- mbNeedle=svgEl(s,'line',{x1:cx,y1:cy,x2:cx,y2:cy-46,stroke:'#1a1613','stroke-width':1.6,'stroke-linecap':'round'});
- svgEl(s,'circle',{cx,cy,r:7,fill:'url(#spadeKnob)',stroke:'#000','stroke-width':.6});
- svgEl(s,'text',{x:14,y:100,'font-size':5,'letter-spacing':'.12em','font-family':'var(--mono)',
- fill:INK,opacity:.8}).textContent='MEGACYCLES';
- // bandspread dial (click = band select)
- const bx=142,by=56;
- for(let i=0;i<12;i++){const a=i*30,[x1,y1]=polar(bx,by,24,a),[x2,y2]=polar(bx,by,21,a);
- svgEl(s,'line',{x1,y1,x2,y2,stroke:INK,'stroke-width':.8});}
- svgEl(s,'circle',{cx:bx,cy:by,r:17,fill:'none',stroke:INK,'stroke-width':.7,opacity:.5});
- mbSpread=svgEl(s,'line',{x1:bx,y1:by,x2:bx,y2:by-22,stroke:'#1a1613','stroke-width':1.4,'stroke-linecap':'round'});
- svgEl(s,'circle',{cx:bx,cy:by,r:6,fill:'url(#spadeKnob)',stroke:'#000','stroke-width':.6});
- svgEl(s,'text',{x:bx,y:96,'text-anchor':'middle','font-size':5,'letter-spacing':'.12em',
- 'font-family':'var(--mono)',fill:INK,opacity:.8}).textContent='BANDSPREAD';
- const dragHit=svgEl(s,'rect',{x:9,y:9,width:100,height:92,fill:'transparent'});
- dragHit.style.cursor='ns-resize';
- dragDelta(dragHit,()=>mbandV,v=>setMband(v,mbandB),{min:0,max:100});
- const clickHit=svgEl(s,'rect',{x:112,y:9,width:69,height:92,fill:'transparent'});
- clickHit.style.cursor='pointer';
- clickHit.addEventListener('click',()=>setMband(mbandV,(mbandB+1)%4));})();
-function setMband(v,b){mbandV=Math.max(0,Math.min(100,v));mbandB=b;
- mbNeedle.setAttribute('transform',`rotate(${-70+mbandV/100*140},62,62)`);
- mbSpread.setAttribute('transform',`rotate(${-45+b*30},142,56)`);
- mbRings.forEach((g,i)=>g.setAttribute('opacity',i===b?'1':'.4'));
- const [lo,hi]=MBAND_RANGES[b];const mc=lo+mbandV/100*(hi-lo);
- setRd('R15',`B${b+1} · ${mc.toFixed(2)} Mc`);}
-setMband(45,2);
+
+
+
/* R16 entry keypad: worn keys feed the amber display; lamps watch state */
let kpBuf='',kpDisp=null,kpLampTop=null,kpLampBot=null;