aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-12 00:28:49 -0500
committerCraig Jennings <c@cjennings.net>2026-07-12 00:28:49 -0500
commitd30bbbc29f796513416fb102d5b55352d68d9295 (patch)
treeba7a8e5bcd36add87e352f266bb2ebc6fcf3e5fa /docs/prototypes
parentd3028a534d450b734eb523167f06e427a242fec9 (diff)
downloadarchsetup-d30bbbc29f796513416fb102d5b55352d68d9295.tar.gz
archsetup-d30bbbc29f796513416fb102d5b55352d68d9295.zip
feat(gallery): add R14 spade-pointer knob and R15 multi-band dial
Two radio-receiver widgets as inline SVG, after a 1920s GE receiver panel and a Hallicrafters shortwave dial pair: - R14 spade-pointer tuning knob: a knurled bakelite knob carries a bright spade pointer over a 0-10 scale rendered as engraved relief (each mark drawn with a light offset twin, ink-free like the source panel). The knurl ring turns with the knob. - R15 multi-band dial: four nested band arcs on a cream face read by one long needle; the active ring draws bold and the rest fade. Dragging the main dial tunes within the active band's frequency range, and clicking the bandspread dial steps bands, turning its own needle. I verified both in headless Chrome: the spade knob tracks drags, tuning moves the needle and readout, the band click switches range and re-weights the rings, no console exceptions. The gallery is at 68 cards.
Diffstat (limited to 'docs/prototypes')
-rw-r--r--docs/prototypes/panel-widget-gallery.html84
1 files changed, 84 insertions, 0 deletions
diff --git a/docs/prototypes/panel-widget-gallery.html b/docs/prototypes/panel-widget-gallery.html
index 890c862..8268935 100644
--- a/docs/prototypes/panel-widget-gallery.html
+++ b/docs/prototypes/panel-widget-gallery.html
@@ -706,6 +706,12 @@ card(C,'R06','Chicken-head selector',
card(C,'R12','Chrome slot fader',
`<svg id="slotf" class="rsvg drag" viewBox="0 0 90 150" width="90" height="150"></svg>`,
'<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>`,
+ '<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>`,
+ '<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.');
/* ============ METERS & GAUGES ============ */
const M=$('meters');
@@ -1470,6 +1476,84 @@ 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);
+
/* ============ LIVE SIGNAL LOOPS ============ */
buildBars($('vuL'),16);buildBars($('vuR'),16);$('mini').innerHTML='<i></i><i></i><i></i><i></i>';
(function(){const eq=$('eq');for(let b=0;b<11;b++){const band=document.createElement('span');band.className='band';