aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-12 00:11:19 -0500
committerCraig Jennings <c@cjennings.net>2026-07-12 00:11:19 -0500
commitd766c6b478dc489ab9e075c61f42cc5dc0b2351b (patch)
treeb87b66ca4037152e502076f417cc061a623b69c6 /docs/prototypes
parent7fff507670e85460130598ac8f293e845c1a0e2a (diff)
downloadarchsetup-d766c6b478dc489ab9e075c61f42cc5dc0b2351b.tar.gz
archsetup-d766c6b478dc489ab9e075c61f42cc5dc0b2351b.zip
feat(gallery): add R05-R07 filter bank, chicken-head selector, round meter
Three more reference-batch widgets as inline SVG, from the Berna-style faceplate set: - R05 filter slider bank: eight band faders with teal double-arrow caps on black tracks, frequency labels up top, a dB rail at the side. Per-band drag; the readout names the band and its attenuation. - R06 chicken-head selector: the tapered lever aims at the engraved position (OFF/LO/MID/HI); the active legend lights. Click steps through. - R07 round panel meter: porthole black bezel, cream dial, printed dB arc with red zone, drag-driven needle. R07 shares the VU-law dB interpolation with R01 through a new vuDb helper, so the nonlinear scale lives in one place. I verified all three in headless Chrome: band drags update cap and readout, the selector steps and relights, the meter needle tracks drags, no console exceptions. The gallery is at 60 cards.
Diffstat (limited to 'docs/prototypes')
-rw-r--r--docs/prototypes/panel-widget-gallery.html103
1 files changed, 100 insertions, 3 deletions
diff --git a/docs/prototypes/panel-widget-gallery.html b/docs/prototypes/panel-widget-gallery.html
index 944657b..fac4762 100644
--- a/docs/prototypes/panel-widget-gallery.html
+++ b/docs/prototypes/panel-widget-gallery.html
@@ -697,6 +697,12 @@ card(C,'R03','Bat-handle toggle',
card(C,'R04','Bakelite fluted knob',
`<svg id="bakek" class="rsvg drag" viewBox="0 0 110 110" width="110" height="110"></svg>`,
'<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>`,
+ '<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>`,
+ '<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.');
/* ============ METERS & GAUGES ============ */
const M=$('meters');
@@ -760,6 +766,9 @@ card(M,'N18','Battery-cell gauge',
card(M,'R01','Moving-coil VU meter',
`<svg id="mcvu" class="rsvg" viewBox="0 0 150 96" width="150" height="96"></svg>`,
'<b>the classic cream-faced VU.</b> Printed dB arc, red zone past 0, ballistic needle. Runs the live signal; readout shows VU. After a vintage console meter pair.');
+card(M,'R07','Round panel meter',
+ `<svg id="rndm" class="rsvg drag" viewBox="0 0 110 104" width="110" height="104"></svg>`,
+ '<b>the porthole lab meter.</b> Cream dial in a round black bezel, printed dB arc, black needle. Drag up/down to drive it; readout shows dB. After a modulator front meter.');
/* ============ INDICATORS & READOUTS ============ */
const I=$('indicators');
@@ -1081,9 +1090,7 @@ const mcvu={t:.35,needle:null};
mcvu.sweep=sweep;})();
function setMcVu(t){t=Math.max(0,Math.min(1.02,t));mcvu.t=t;
mcvu.needle.setAttribute('transform',`rotate(${mcvu.sweep(Math.min(1,t))},75,112)`);
- let i=0;while(i<VUDB.length-2&&VUDB[i+1][1]<t)i++;
- const [d1,t1]=VUDB[i],[d2,t2]=VUDB[i+1];
- const db=d1+(d2-d1)*Math.max(0,Math.min(1,(t-t1)/(t2-t1)));
+ const db=vuDb(Math.min(1,t));
setRd('R01',(db>0?'+':'')+db.toFixed(1)+' VU');}
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));}
@@ -1174,6 +1181,96 @@ function setBake(v){bakeV=v;bakeIdx.setAttribute('transform',`rotate(${v*2.7-135
setRd('R04',(v/10).toFixed(1)+' / 10');}
dragDelta($('bakek'),()=>bakeV,setBake,{min:0,max:100,sens:0.25});setBake(63);
+/* shared: interpolate a dB reading from a 0..1 sweep position (VU law) */
+function vuDb(t){let i=0;while(i<VUDB.length-2&&VUDB[i+1][1]<t)i++;
+ const [d1,t1]=VUDB[i],[d2,t2]=VUDB[i+1];
+ return d1+(d2-d1)*Math.max(0,Math.min(1,(t-t1)/(t2-t1)));}
+
+/* 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);
+
+/* R07 round panel meter: porthole bezel, same VU law as R01 */
+let rndT=.5,rndNeedle=null;
+(function(){const s=$('rndm'),cx=55,fy=54,py=74,sweep=t=>-40+t*80;
+ const defs=svgEl(s,'defs',{});
+ const clip=svgEl(defs,'clipPath',{id:'rndFace'});
+ svgEl(clip,'circle',{cx,cy:fy,r:40});
+ svgEl(s,'circle',{cx,cy:fy,r:48,fill:'#141110',stroke:'#060505','stroke-width':2});
+ svgEl(s,'circle',{cx,cy:fy,r:40,fill:'var(--cream)'});
+ const g=svgEl(s,'g',{'clip-path':'url(#rndFace)'});
+ const pt=(r,t)=>polar(cx,py,r,sweep(t));
+ const [ax1,ay1]=pt(44,0),[ax2,ay2]=pt(44,1);
+ svgEl(g,'path',{d:`M ${ax1} ${ay1} A 44 44 0 0 1 ${ax2} ${ay2}`,fill:'none',stroke:'#3a3128','stroke-width':1.1});
+ const [rzx,rzy]=pt(44,.75);
+ svgEl(g,'path',{d:`M ${rzx} ${rzy} A 44 44 0 0 1 ${ax2} ${ay2}`,fill:'none',stroke:'var(--fail)','stroke-width':3});
+ for(const [db,t] of VUDB){const [x1,y1]=pt(41.5,t),[x2,y2]=pt(47,t);
+ svgEl(g,'line',{x1,y1,x2,y2,stroke:db>0?'var(--fail)':'#3a3128','stroke-width':1});}
+ for(const [db,t] of VUDB){if(![-20,-10,-5,-3,0,3].includes(db))continue;
+ const [x,y]=pt(52,t);
+ svgEl(g,'text',{x,y:y+2,'text-anchor':'middle','font-size':5.5,'font-family':'var(--mono)',
+ fill:db>0?'var(--fail)':'#3a3128'}).textContent=Math.abs(db);}
+ svgEl(g,'text',{x:cx,y:fy+14,'text-anchor':'middle','font-size':8,'font-weight':700,
+ 'font-family':'var(--mono)',fill:'#3a3128'}).textContent='dB';
+ rndNeedle=svgEl(g,'line',{x1:cx,y1:py,x2:cx,y2:py-46,stroke:'#1a1613','stroke-width':1.5,
+ transform:`rotate(0,${cx},${py})`});
+ svgEl(g,'circle',{cx,cy:py,r:5,fill:'#141110'});
+ svgEl(s,'ellipse',{cx:cx-8,cy:fy-22,rx:20,ry:9,fill:'rgba(255,255,255,.07)',transform:`rotate(-18,${cx-8},${fy-22})`});})();
+function setRnd(v){const t=Math.max(0,Math.min(1,v/100));rndT=t;
+ rndNeedle.setAttribute('transform',`rotate(${-40+t*80},55,74)`);
+ const db=vuDb(t);setRd('R07',(db>0?'+':'')+db.toFixed(1)+' dB');}
+dragDelta($('rndm'),()=>rndT*100,setRnd,{min:0,max:100});setRnd(50);
+
/* ============ 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';