aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-12 01:06:16 -0500
committerCraig Jennings <c@cjennings.net>2026-07-12 01:06:16 -0500
commitb288d529c50185c291fcdbe6dd3a3c18c543696e (patch)
tree70cc4f57095c1456ba1f6dcbd73cc678e9bd5838
parent8fa5fa4f2a7a1cc43cbcb5abdc623e4aac66346e (diff)
downloadarchsetup-b288d529c50185c291fcdbe6dd3a3c18c543696e.tar.gz
archsetup-b288d529c50185c291fcdbe6dd3a3c18c543696e.zip
feat(gallery): add R26 response graph
A frequency-domain plot after a mastering processor's response display (references filed in the catalogue's working directory): log-frequency grid from 32 Hz to 16 kHz with labeled dB rails, an amber bell curve drawn live from a peak the user places, and a handle draggable in both axes at once. The kit's other traces are time-domain; this is its first x/y function display and its first two-axis drag. I verified it in headless Chrome: press places the peak, dragging moves it in frequency and gain together, the curve and readout follow, no console exceptions.
-rw-r--r--docs/prototypes/panel-widget-gallery.html47
-rw-r--r--working/retro-stereo-widgets/references/2026-07-12-waves-alpha.pngbin0 -> 68190 bytes
2 files changed, 47 insertions, 0 deletions
diff --git a/docs/prototypes/panel-widget-gallery.html b/docs/prototypes/panel-widget-gallery.html
index ac2af6a..815fc3c 100644
--- a/docs/prototypes/panel-widget-gallery.html
+++ b/docs/prototypes/panel-widget-gallery.html
@@ -879,6 +879,9 @@ card(I,'R11','Warning flag window',
card(I,'R25','Fourteen-segment display',
`<svg id="seg14" class="rsvg press" viewBox="0 0 130 54" width="130" height="54"></svg>`,
'<b>segments that spell.</b> The starburst alphanumeric — diagonals and split bars let it write words, not just digits. Click to cycle the word. After a pedal preset display.');
+card(I,'R26','Response graph',
+ `<svg id="rgraph" class="rsvg" viewBox="0 0 190 110" width="190" height="110"></svg>`,
+ '<b>a curve on labeled axes.</b> Log frequency across, dB up the side, and the amber peak is yours to place — drag it anywhere on the plot. After the response display of a mastering processor.');
/* ============ CONTROL WIRING ============ */
/* 01 toggle */
@@ -1927,6 +1930,50 @@ function setSeg14(i){seg14I=i;const word=SEG14_WORDS[i].padEnd(4,' ');
setRd('R25',SEG14_WORDS[i]);}
setSeg14(0);
+/* R26 response graph: log-frequency axes, a draggable amber peak */
+const RG={x0:30,x1:182,y0:8,y1:86,fLo:Math.log10(32),fHi:Math.log10(16000),db:12};
+let rgFc=1200,rgGain=6.5,rgCurve=null,rgGlowLine=null,rgHandle=null;
+(function(){const s=$('rgraph');
+ svgEl(s,'rect',{x:1,y:1,width:188,height:108,rx:5,fill:'#0d1410',stroke:'#22301f','stroke-width':2});
+ const FREQS=[32,64,128,250,500,1000,2000,4000,8000,16000];
+ FREQS.forEach(f=>{const x=RG.x0+(Math.log10(f)-RG.fLo)/(RG.fHi-RG.fLo)*(RG.x1-RG.x0);
+ svgEl(s,'line',{x1:x,y1:RG.y0,x2:x,y2:RG.y1,stroke:'#22301f','stroke-width':.6});
+ svgEl(s,'text',{x,y:RG.y1+9,'text-anchor':'middle','font-size':4.8,'font-family':'var(--mono)',
+ fill:'var(--steel)'}).textContent=f<1000?f:(f/1000)+'k';});
+ for(const db of [12,6,0,-6,-12]){const y=RG.y0+(RG.db-db)/(2*RG.db)*(RG.y1-RG.y0);
+ svgEl(s,'line',{x1:RG.x0,y1:y,x2:RG.x1,y2:y,stroke:'#22301f','stroke-width':db===0?1:.6});
+ svgEl(s,'text',{x:RG.x0-4,y:y+2,'text-anchor':'end','font-size':4.8,'font-family':'var(--mono)',
+ fill:'var(--steel)'}).textContent=(db>0?'+':'')+db;}
+ rgGlowLine=svgEl(s,'polyline',{points:'',fill:'none',stroke:'var(--gold-hi)','stroke-width':3,
+ opacity:.35,filter:'url(#avGlow)'});
+ rgCurve=svgEl(s,'polyline',{points:'',fill:'none',stroke:'var(--gold)','stroke-width':1.6});
+ rgHandle=svgEl(s,'circle',{r:4,fill:'var(--gold-hi)',stroke:'#7d5c16','stroke-width':1});
+ rgHandle.style.cursor='move';
+ // 2D drag: the handle (and the plot) place the peak in both axes at once
+ s.style.touchAction='none';
+ s.addEventListener('pointerdown',e=>{s.setPointerCapture(e.pointerId);
+ const move=ev=>{const r=s.getBoundingClientRect(),sx=190/r.width,sy=110/r.height;
+ const px=(ev.clientX-r.left)*sx,py=(ev.clientY-r.top)*sy;
+ const lf=RG.fLo+Math.max(0,Math.min(1,(px-RG.x0)/(RG.x1-RG.x0)))*(RG.fHi-RG.fLo);
+ const g=RG.db-Math.max(0,Math.min(1,(py-RG.y0)/(RG.y1-RG.y0)))*2*RG.db;
+ setRgraph(Math.pow(10,lf),g);};
+ move(e);
+ const up=()=>{s.removeEventListener('pointermove',move);s.removeEventListener('pointerup',up);s.removeEventListener('pointercancel',up);};
+ s.addEventListener('pointermove',move);s.addEventListener('pointerup',up);s.addEventListener('pointercancel',up);
+ e.preventDefault();});})();
+function setRgraph(fc,gain){rgFc=Math.max(32,Math.min(16000,fc));rgGain=Math.max(-12,Math.min(12,gain));
+ const xOf=f=>RG.x0+(Math.log10(f)-RG.fLo)/(RG.fHi-RG.fLo)*(RG.x1-RG.x0);
+ const yOf=db=>RG.y0+(RG.db-db)/(2*RG.db)*(RG.y1-RG.y0);
+ const sigma=0.28;let pts='';
+ for(let i=0;i<=76;i++){const lf=RG.fLo+i/76*(RG.fHi-RG.fLo);
+ const db=rgGain*Math.exp(-Math.pow(lf-Math.log10(rgFc),2)/(2*sigma*sigma));
+ pts+=`${xOf(Math.pow(10,lf)).toFixed(1)},${yOf(db).toFixed(1)} `;}
+ rgCurve.setAttribute('points',pts.trim());rgGlowLine.setAttribute('points',pts.trim());
+ rgHandle.setAttribute('cx',xOf(rgFc));rgHandle.setAttribute('cy',yOf(rgGain));
+ const fLbl=rgFc<1000?Math.round(rgFc)+' Hz':(rgFc/1000).toFixed(1)+' kHz';
+ setRd('R26',`${fLbl} · ${rgGain>=0?'+':''}${rgGain.toFixed(1)} dB`);}
+setRgraph(1200,6.5);
+
/* ============ 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';
diff --git a/working/retro-stereo-widgets/references/2026-07-12-waves-alpha.png b/working/retro-stereo-widgets/references/2026-07-12-waves-alpha.png
new file mode 100644
index 0000000..3adccf8
--- /dev/null
+++ b/working/retro-stereo-widgets/references/2026-07-12-waves-alpha.png
Binary files differ