diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/prototypes/panel-widget-gallery.html | 69 | ||||
| -rw-r--r-- | docs/prototypes/widgets.js | 169 |
2 files changed, 190 insertions, 48 deletions
diff --git a/docs/prototypes/panel-widget-gallery.html b/docs/prototypes/panel-widget-gallery.html index 082c503..beb735b 100644 --- a/docs/prototypes/panel-widget-gallery.html +++ b/docs/prototypes/panel-widget-gallery.html @@ -1713,31 +1713,30 @@ card(C,'R51','Voice-loop keyset', /* ============ METERS & GAUGES ============ */ const M=$('meters'); card(M,'10','Needle gauge', - `<div class="gauge" id="gauge10"><div class="dial"><div class="arc"></div> - <div class="tk" style="transform:rotate(-60deg)"></div><div class="tk" style="transform:rotate(0)"></div><div class="tk" style="transform:rotate(60deg)"></div> - <div class="ndl" id="g1"></div><div class="hub"></div></div><div class="gv"><span id="g1v">0</span>%</div></div>`, + (st,rd)=>GW.needleGauge(st,{value:50,onChange:(v,t)=>rd(t)}), '<b>analog dial.</b> Throughput, battery, volume. Drag up/down to sweep the needle; readout tracks. <b>Smooth sweep wants a Cairo/GTK area.</b>'); card(M,'11','Stereo VU (LED bar)', - `<div class="vu"><div class="vurow"><span class="ch">L</span><span class="vubar" id="vuL"></span></div> - <div class="vurow"><span class="ch">R</span><span class="vubar" id="vuR"></span></div></div>`, + (st,rd)=>GW.vuPair(st,{onChange:(v,t)=>rd(t)}), '<b>live signal level.</b> The sound panel meter row, peak-hold outline. Runs a live signal; readout shows L/R.'); card(M,'12','Mini signal (4-bar)', - `<span class="sig" id="mini"></span>`, + (st,rd)=>GW.miniSig(st,{onChange:(v,t)=>rd(t)}), '<b>compact activity.</b> Per-row "is this device playing". Live signal; readout shows activity %.'); card(M,'13','Signal ladder', - `<span class="ladder" id="ladder"><i></i><i></i><i></i><i></i></span>`, + (st,rd)=>GW.signalLadder(st,{value:3,onChange:(v,t)=>rd(t)}), '<b>discrete strength.</b> Wifi bars, bt RSSI — a stepped 0-4. Click to cycle strength; readout shows bars.'); card(M,'14','Linear fuel bar', - `<div class="bar" id="bar14"><span style="width:72%"></span></div><div class="bar" id="bar14b"><span style="width:40%"></span></div>`, + (st,rd)=>{const a=GW.fuelBar(st,{value:72,onChange:(v,t)=>rd(t)}); + const b=GW.fuelBar(st,{value:40,onChange:(v,t)=>rd(t)}); + a.set(72);return {a,b};}, '<b>a single 0-100.</b> Battery, disk, download. Drag either bar; warn tint under threshold; readout tracks.'); card(M,'15','Radial ring', - `<span class="ring" id="ring15" style="--p:68"><b>68</b></span>`, + (st,rd)=>GW.radialRing(st,{value:68,onChange:(v,t)=>rd(t)}), '<b>percentage as a donut.</b> CPU, battery. Drag up/down to set; conic-gradient tracks the readout.'); card(M,'16','Sparkline', - `<span class="spark" id="spark"><svg viewBox="0 0 170 44" preserveAspectRatio="none"><polyline id="sparkp" fill="none" stroke="var(--gold-hi)" stroke-width="1.5"/></svg></span>`, + (st,rd)=>GW.sparkline(st,{onChange:(v,t)=>rd(t)}), '<b>recent history.</b> Throughput/CPU over the last minute. Live trace; readout shows the current value.'); card(M,'17','Waveform strip', - `<span class="wave" id="wave"><svg viewBox="0 0 170 38" preserveAspectRatio="none"><path id="wavep" fill="none" stroke="var(--gold)" stroke-width="1.2"/></svg></span>`, + (st,rd)=>GW.waveStrip(st,{onChange:(v,t)=>rd(t)}), '<b>audio waveform / scope.</b> A richer signal view. Live trace; readout shows amplitude. <b>Needs a drawing surface.</b>'); card(M,'N11','Oscilloscope', `<span class="scope"><span class="grat"></span><svg viewBox="0 0 176 78" preserveAspectRatio="none"><polyline id="scopep"/></svg></span>`, @@ -1905,23 +1904,7 @@ card(I,'R52','Blinkenlights front panel', /* 01-09, 24, 25, N01-N10 extracted to widgets.js (GW.*) — wired at their card records */ /* ============ METER WIRING (drag-driven) ============ */ -/* 10 needle gauge */ -let gV=50; -function setGauge(v){gV=v;$('g1').style.transform=`rotate(${-60+v/100*120}deg)`;$('g1v').textContent=Math.round(v);setRd('10','value '+Math.round(v)+'%');} -dragDelta($('gauge10'),()=>gV,setGauge,{min:0,max:100});setGauge(50); -/* 13 signal ladder */ -let ladV=3; -function setLadder(v){ladV=v;const bars=$('ladder').children; - for(let i=0;i<bars.length;i++)bars[i].style.background=i<v?'var(--gold)':'var(--wash)';setRd('13',v+'/4');} -$('ladder').onclick=()=>setLadder((ladV+1)%5);setLadder(3); -/* 14 fuel bars */ -function setBar(id,no,p){const b=$(id);b.querySelector('span').style.width=p+'%';b.classList.toggle('warn',p<20);setRd(no,Math.round(p)+'%');} -dragX($('bar14'),p=>setBar('bar14','14',p));dragX($('bar14b'),p=>setBar('bar14b','14',p)); -setBar('bar14','14',72); -/* 15 radial ring */ -let ringV=68; -function setRing(v){ringV=v;const r=$('ring15');r.style.setProperty('--p',v);r.querySelector('b').textContent=Math.round(v);setRd('15',Math.round(v)+'%');} -dragDelta($('ring15'),()=>ringV,setRing,{min:0,max:100});setRing(68); +/* 10-17 extracted to widgets.js (GW.*) — wired at their card records */ /* N13 crossed-needle */ let cnPow=55; function setCross(v){cnPow=v;const fwd=v,rfl=v*0.68;$('cnA').style.transform=`rotate(${-42+fwd/100*84}deg)`; @@ -2454,7 +2437,8 @@ function setRgraph(fc,gain){rgFc=Math.max(32,Math.min(16000,fc));rgGain=Math.max setRgraph(1200,6.5); /* ============ LIVE SIGNAL LOOPS ============ */ -buildBars($('vuL'),16);buildBars($('vuR'),16);$('mini').innerHTML='<i></i><i></i><i></i><i></i>'; +/* extracted meters: the page keeps the clock + demo signal, drives card handles */ +const MH={vu:$('card-11').gw,mini:$('card-12').gw,spark:$('card-16').gw,wave:$('card-17').gw}; (function(){const eq=$('eq');for(let b=0;b<11;b++){const band=document.createElement('span');band.className='band'; for(let s=0;s<9;s++)band.appendChild(document.createElement('i'));eq.appendChild(band);}})(); (function(){const cl=$('clock');for(let i=0;i<12;i++){const t=document.createElement('span');t.className='tk'; @@ -2467,14 +2451,8 @@ const counterDigits=[4,7,1,3,0,0]; function setWheel(w,d){w.querySelector('.col').style.top=(-(d+1)*34)+'px';} let ph=0; -const hist=Array.from({length:40},()=>0.5); const eqBands=Array.from({length:11},(_,i)=>({v:0.4,ph:i*0.6})); const stripHist=Array.from({length:60},()=>31); -const pkL={v:0},pkR={v:0}; -function paintVU(el,l,pk){const b=el.children,n=b.length,lit=Math.round(l*n); - pk.v=Math.max(lit,(pk.v||0)-0.4);const p=Math.round(pk.v); - for(let k=0;k<n;k++){let c=k<lit?(k>=n-2?'clip':k>=n-4?'hot':'on'):'';if(p>0&&k===p-1)c=(c?c+' ':'')+'peak';b[k].className=c;}} -function paintMini(el,l){const b=el.children,lit=Math.round(l*4);for(let k=0;k<4;k++)b[k].className=k<lit?(k>=3?'clip':k>=2?'hot':'on'):'';} function paintEq(){const bands=$('eq').children;let peak=0; for(let b=0;b<bands.length;b++){const cells=bands[b].children,n=cells.length,val=eqBands[b].v;const lit=Math.round(val*n);peak=Math.max(peak,val); for(let k=0;k<n;k++){let c='';if(k<lit)c=(k>=n-1?'clip':k>=n-3?'hot':'on');cells[k].className=c?c:'';}}return peak;} @@ -2482,15 +2460,11 @@ function lvl(){return Math.max(0,Math.min(1,0.5+0.4*Math.sin(ph*1.3)+(Math.rando function fastTick(){ ph+=0.09; const a=lvl(),b=lvl(); - paintVU($('vuL'),a,pkL);paintVU($('vuR'),b,pkR);paintMini($('mini'),a); - setRd('11','L '+Math.round(a*100)+' · R '+Math.round(b*100)); - setRd('12',Math.round(a*100)+'%'); - hist.push(0.5+0.42*Math.sin(ph*0.9)+(Math.random()-0.5)*0.25);hist.shift(); - $('sparkp').setAttribute('points',hist.map((v,i)=>`${i/(hist.length-1)*170},${44-Math.max(0,Math.min(1,v))*40-2}`).join(' ')); - setRd('16',Math.round(Math.max(0,Math.min(1,hist[hist.length-1]))*100)); - let d='M0 19';const env=Math.min(1,0.6+0.3*Math.sin(ph*0.4)); - for(let x=0;x<=170;x+=3){const y=19+Math.sin(x*0.18+ph*3)*Math.sin(x*0.05)*14;d+=` L${x} ${y.toFixed(1)}`;} - $('wavep').setAttribute('d',d);setRd('17','amp '+Math.round(env*100)+'%'); + MH.vu.set(a,b);MH.mini.set(a); + MH.spark.push(0.5+0.42*Math.sin(ph*0.9)+(Math.random()-0.5)*0.25); + const env=Math.min(1,0.6+0.3*Math.sin(ph*0.4)); + const smp=[];for(let x=0;x<=170;x+=3)smp.push(Math.sin(x*0.18+ph*3)*Math.sin(x*0.05)); + MH.wave.set(smp,env); let sp='';for(let x=0;x<=176;x+=3){const y=39+Math.sin(x*0.16+ph*3.2)*Math.min(1,0.6+0.3*Math.sin(ph*0.4))*22;sp+=`${x},${y.toFixed(1)} `;} $('scopep').setAttribute('points',sp.trim());setRd('N11','Vpp '+Math.round((0.6+0.3*Math.sin(ph*0.4))*100)); eqBands.forEach(bd=>{bd.ph+=0.16;bd.v=Math.max(0.05,Math.min(1,0.5+0.42*Math.sin(bd.ph)+(Math.random()<0.2?Math.random()*0.3:0)-Math.random()*0.06));}); @@ -2541,10 +2515,9 @@ document.querySelectorAll('.reel.spin').forEach(r=>{r.style.animation='reelspin /* static paint for reduced motion */ function paintStatic(){ - paintVU($('vuL'),0.55,pkL);paintVU($('vuR'),0.5,pkR);paintMini($('mini'),0.55); - setRd('11','L 55 · R 50');setRd('12','55%'); - $('sparkp').setAttribute('points',hist.map((v,i)=>`${i/(hist.length-1)*170},${44-0.5*40-2}`).join(' '));setRd('16','50'); - $('wavep').setAttribute('d','M0 19 L170 19');setRd('17','amp 60%'); + MH.vu.set(0.55,0.5);MH.mini.set(0.55); + MH.spark.fill(0.5); + MH.wave.set([],0.6); eqBands.forEach((bd,i)=>bd.v=0.3+0.5*Math.abs(Math.sin(i*0.9)));setRd('N12','peak '+Math.round(paintEq()*100)+'%'); $('scopep').setAttribute('points',Array.from({length:60},(_,i)=>`${i*3},${39+Math.sin(i*0.5)*18}`).join(' '));setRd('N11','Vpp 74'); $('stripp').setAttribute('points',stripHist.map((v,i)=>`${i/(stripHist.length-1)*176},${31-Math.sin(i*0.3)*16}`).join(' '));setRd('N16','50'); diff --git a/docs/prototypes/widgets.js b/docs/prototypes/widgets.js index ed0feb4..266cdac 100644 --- a/docs/prototypes/widgets.js +++ b/docs/prototypes/widgets.js @@ -1806,6 +1806,175 @@ GW.voiceLoop = function (host, opts = {}) { return { el, get: () => keys.map(k => k.dataset.state) }; }; +/* ---- meters & gauges ---- + Tick contract: the page owns the clock and the signal; live meters expose + value-driven handles (set/push) that repaint synchronously and fire + onChange(value, text) like every other builder. Display-side state that + belongs to the instrument (peak-hold, history buffers) lives in here. */ + +/* 10 needle gauge — drag up/down sweeps the needle over a 120° arc */ +GW.needleGauge = function (host, opts = {}) { + const onChange = opts.onChange || noop; + let v = opts.value ?? 50; + const el = document.createElement('div'); el.className = 'gauge'; + el.innerHTML = `<div class="dial"><div class="arc"></div> + <div class="tk" style="transform:rotate(-60deg)"></div><div class="tk" style="transform:rotate(0)"></div><div class="tk" style="transform:rotate(60deg)"></div> + <div class="ndl"></div><div class="hub"></div></div><div class="gv"><span>0</span>%</div>`; + host.appendChild(el); + const ndl = el.querySelector('.ndl'), num = el.querySelector('.gv span'); + function set(nv) { + v = nv; + ndl.style.transform = `rotate(${-60 + v / 100 * 120}deg)`; + num.textContent = Math.round(v); + onChange(v, 'value ' + Math.round(v) + '%'); + } + dragDelta(el, () => v, set, { min: 0, max: 100 }); + set(v); + return { el, get: () => v, set }; +}; + +/* 11 stereo VU — two LED bars with peak-hold; set(l, r) drives both */ +GW.vuPair = function (host, opts = {}) { + const onChange = opts.onChange || noop; + const n = opts.bars || 16; + const el = document.createElement('div'); el.className = 'vu'; + el.innerHTML = `<div class="vurow"><span class="ch">L</span><span class="vubar"></span></div> + <div class="vurow"><span class="ch">R</span><span class="vubar"></span></div>`; + host.appendChild(el); + const bars = el.querySelectorAll('.vubar'); + bars.forEach(b => buildBars(b, n)); + const pkL = { v: 0 }, pkR = { v: 0 }; + function paint(bar, l, pk) { + const b = bar.children, lit = Math.round(l * n); + pk.v = Math.max(lit, (pk.v || 0) - 0.4); const p = Math.round(pk.v); + for (let k = 0; k < n; k++) { + let c = k < lit ? (k >= n - 2 ? 'clip' : k >= n - 4 ? 'hot' : 'on') : ''; + if (p > 0 && k === p - 1) c = (c ? c + ' ' : '') + 'peak'; + b[k].className = c; + } + } + let lv = 0, rv = 0; + function set(l, r) { + lv = l; rv = r; + paint(bars[0], l, pkL); paint(bars[1], r, pkR); + onChange([l, r], 'L ' + Math.round(l * 100) + ' · R ' + Math.round(r * 100)); + } + return { el, get: () => [lv, rv], set }; +}; + +/* 12 mini 4-bar signal — compact activity meter; set(level) */ +GW.miniSig = function (host, opts = {}) { + const onChange = opts.onChange || noop; + const el = document.createElement('span'); el.className = 'sig'; + el.innerHTML = '<i></i><i></i><i></i><i></i>'; + host.appendChild(el); + let v = 0; + function set(l) { + v = l; + const b = el.children, lit = Math.round(l * 4); + for (let k = 0; k < 4; k++) b[k].className = k < lit ? (k >= 3 ? 'clip' : k >= 2 ? 'hot' : 'on') : ''; + onChange(l, Math.round(l * 100) + '%'); + } + return { el, get: () => v, set }; +}; + +/* 13 signal ladder — stepped 0-4 strength; click cycles */ +GW.signalLadder = function (host, opts = {}) { + const onChange = opts.onChange || noop; + let v = opts.value ?? 3; + const el = document.createElement('span'); el.className = 'ladder'; + el.innerHTML = '<i></i><i></i><i></i><i></i>'; + host.appendChild(el); + function set(nv) { + v = nv; + const bars = el.children; + for (let i = 0; i < bars.length; i++) bars[i].style.background = i < v ? 'var(--gold)' : 'var(--wash)'; + onChange(v, v + '/4'); + } + el.onclick = () => set((v + 1) % 5); + set(v); + return { el, get: () => v, set }; +}; + +/* 14 linear fuel bar — one 0-100 bar, warn tint under the threshold; drag to set */ +GW.fuelBar = function (host, opts = {}) { + const onChange = opts.onChange || noop; + const warnAt = opts.warnAt ?? 20; + let v = opts.value ?? 50; + const el = document.createElement('div'); el.className = 'bar'; + el.innerHTML = '<span></span>'; + host.appendChild(el); + const fill = el.querySelector('span'); + function set(p) { + v = p; + fill.style.width = p + '%'; + el.classList.toggle('warn', p < warnAt); + onChange(p, Math.round(p) + '%'); + } + dragX(el, set); + set(v); + return { el, get: () => v, set }; +}; + +/* 15 radial ring — percentage donut; drag up/down to set */ +GW.radialRing = function (host, opts = {}) { + const onChange = opts.onChange || noop; + let v = opts.value ?? 68; + const el = document.createElement('span'); el.className = 'ring'; + el.innerHTML = '<b></b>'; + host.appendChild(el); + const num = el.querySelector('b'); + function set(nv) { + v = nv; + el.style.setProperty('--p', v); + num.textContent = Math.round(v); + onChange(v, Math.round(v) + '%'); + } + dragDelta(el, () => v, set, { min: 0, max: 100 }); + set(v); + return { el, get: () => v, set }; +}; + +/* 16 sparkline — rolling history trace; push(v) appends a sample, fill(v) levels it */ +GW.sparkline = function (host, opts = {}) { + const onChange = opts.onChange || noop; + const n = opts.samples || 40; + const hist = Array.from({ length: n }, () => opts.value ?? 0.5); + const el = document.createElement('span'); el.className = 'spark'; + el.innerHTML = '<svg viewBox="0 0 170 44" preserveAspectRatio="none"><polyline fill="none" stroke="var(--gold-hi)" stroke-width="1.5"/></svg>'; + host.appendChild(el); + const line = el.querySelector('polyline'); + const clamp = x => Math.max(0, Math.min(1, x)); + function paint() { + line.setAttribute('points', hist.map((v, i) => `${i / (n - 1) * 170},${44 - clamp(v) * 40 - 2}`).join(' ')); + onChange(hist[n - 1], String(Math.round(clamp(hist[n - 1]) * 100))); + } + function push(v) { hist.push(v); hist.shift(); paint(); } + function fill(v) { hist.fill(v); paint(); } + return { el, get: () => hist[n - 1], push, fill }; +}; + +/* 17 waveform strip — sampled trace; set(samples, amp) with samples in -1..1 */ +GW.waveStrip = function (host, opts = {}) { + const onChange = opts.onChange || noop; + const el = document.createElement('span'); el.className = 'wave'; + el.innerHTML = '<svg viewBox="0 0 170 38" preserveAspectRatio="none"><path fill="none" stroke="var(--gold)" stroke-width="1.2"/></svg>'; + host.appendChild(el); + const path = el.querySelector('path'); + let amp = 0; + function set(samples, a) { + amp = a; + let d = 'M0 19'; + if (samples.length < 2) d += ' L170 19'; + else for (let i = 0; i < samples.length; i++) + d += ` L${(i / (samples.length - 1) * 170).toFixed(1)} ${(19 + samples[i] * 14).toFixed(1)}`; + path.setAttribute('d', d); + onChange(amp, 'amp ' + Math.round(amp * 100) + '%'); + } + set([], opts.amp ?? 0.6); + return { el, get: () => amp, set }; +}; + /* ---- widget CSS: injected once, grows as builders move in ---- */ const GW_CSS = ``; function ensureCss() { |
