Some checks failed
check / check (push) Has been cancelled
First commit of the repo. Contents beyond the v0.1 simulator and layout: - Full Pico 2 W firmware (firmware/app, drivers, engine port, host test). - BANK page and 404 mode A/B removed; learned pads play exactly what was learned. - 404/MiniFreak renamed to KIT/SYNTH throughout (jacks, code, sim, docs, layout labels; wiring rebuilt). - ROLL returns to page select after a re-roll; a freshly learned track turns on. - LCD contrast 66. - ROOT and SCALE pages: 21 scales, AUTO = the seed picks; global and sticky. Intended music change: the scale table grew from 8 to 21 and AUTO picks from all of them, so every seed's scale changed. Vectors regenerated with npm run vectors, plus new key-override cases. Flash record is now version 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
271 lines
21 KiB
HTML
271 lines
21 KiB
HTML
<title>Seedbox Panel Layout</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=Martian+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
/* Layout: to-scale drawing of the box unfolded (end wall, lid, side wall) beside its inputs; drill table below. */
|
||
:root{
|
||
--bg:#E8ECEF; --surface:#F6F7F9; --ink:#18202A; --muted:#566271; --line:#C3CBD3; --accent:#1E4C9E; --warn:#B4441F;
|
||
--metal:#23272C; --metal-edge:#3A4047; --print:#D9DEE3; --pcb:#B8282A; --lcd:#A6B59A;
|
||
--f-ui:'Hanken Grotesk', system-ui, -apple-system, 'Segoe UI', sans-serif;
|
||
--f-mono:'Martian Mono', ui-monospace, Menlo, Consolas, monospace;
|
||
}
|
||
@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
|
||
--bg:#12161B; --surface:#1A1F25; --ink:#E2E7EC; --muted:#8C98A5; --line:#333B44; --accent:#8EB0F2; --warn:#F07B52;
|
||
--metal:#0B0D10; --metal-edge:#4A525B; color-scheme:dark } }
|
||
:root[data-theme="dark"]{
|
||
--bg:#12161B; --surface:#1A1F25; --ink:#E2E7EC; --muted:#8C98A5; --line:#333B44; --accent:#8EB0F2; --warn:#F07B52;
|
||
--metal:#0B0D10; --metal-edge:#4A525B; color-scheme:dark }
|
||
*{box-sizing:border-box}
|
||
body{background:var(--bg);color:var(--ink);font:15px/1.55 var(--f-ui);padding-block:28px 48px;padding-inline:16px}
|
||
.wrap{max-width:1100px;margin:0 auto;display:grid;gap:22px}
|
||
h1{font:700 24px/1.15 var(--f-mono);margin:0}
|
||
h2{font:700 13px var(--f-mono);letter-spacing:.08em;text-transform:uppercase;color:var(--accent);margin:0}
|
||
.sub{color:var(--muted);margin:6px 0 0;max-width:72ch}
|
||
.main{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:22px;align-items:start}
|
||
@media (max-width:860px){.main{grid-template-columns:minmax(0,1fr)}}
|
||
.draw{background:var(--surface);border:1px solid var(--line);border-radius:10px;padding:14px;min-width:0}
|
||
.draw svg{display:block;width:100%;height:auto;max-height:82vh}
|
||
.side{display:grid;gap:14px}
|
||
.card{background:var(--surface);border:1px solid var(--line);border-radius:10px;padding:14px 16px;display:grid;gap:10px}
|
||
.fields{display:grid;grid-template-columns:1fr 1fr;gap:10px 12px}
|
||
.field{display:grid;gap:4px;min-width:0}
|
||
.field label{font:500 11px var(--f-mono);color:var(--muted)}
|
||
input[type=number]{font:500 14px var(--f-mono);color:var(--ink);background:var(--bg);border:1px solid var(--line);border-radius:6px;padding:6px 8px;width:100%}
|
||
input:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
||
.seg{display:grid;grid-template-columns:1fr 1fr;border:1px solid var(--line);border-radius:7px;overflow:hidden}
|
||
.segb{font:600 12px var(--f-mono);padding:8px;border:0;background:var(--bg);color:var(--muted);cursor:pointer}
|
||
.segb[aria-pressed="true"]{background:var(--accent);color:var(--surface)}
|
||
.segb:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
|
||
.warns{display:grid;gap:6px;font-size:13px}
|
||
.warns .ok{color:var(--muted)}
|
||
.warns .bad{color:var(--warn);font-weight:600}
|
||
.note{color:var(--muted);font-size:13px;margin:0}
|
||
.tbl{overflow-x:auto;background:var(--surface);border:1px solid var(--line);border-radius:10px}
|
||
table{width:100%;border-collapse:collapse;min-width:560px}
|
||
th{font:600 10px var(--f-mono);letter-spacing:.08em;text-transform:uppercase;color:var(--muted);text-align:left;padding:9px 12px;border-bottom:1px solid var(--line)}
|
||
td{padding:8px 12px;border-bottom:1px solid var(--line);font-size:14px}
|
||
td.n{font:500 13px var(--f-mono);font-variant-numeric:tabular-nums;white-space:nowrap}
|
||
tr:last-child td{border-bottom:0}
|
||
.cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px}
|
||
.cols ul{margin:0;padding-left:18px;display:grid;gap:4px;font-size:14px}
|
||
</style>
|
||
|
||
<div class="wrap">
|
||
<header>
|
||
<h1>Seedbox panel layout</h1>
|
||
<p class="sub">Lid and walls, drawn to scale and unfolded, in portrait or landscape. The box size is an estimate from your photo, using the red Nokia module (about 44 mm square) as the ruler. Measure the real box and type it in: everything re-flows and the drill table updates.</p>
|
||
</header>
|
||
|
||
<div class="main">
|
||
<div class="draw"><svg id="svg" role="img" aria-label="To-scale panel layout"></svg></div>
|
||
<div class="side">
|
||
<div class="card">
|
||
<h2>Box</h2>
|
||
<div class="seg" role="group" aria-label="Orientation">
|
||
<button type="button" class="segb" id="oP" aria-pressed="false">Portrait</button>
|
||
<button type="button" class="segb" id="oL" aria-pressed="true">Landscape</button>
|
||
</div>
|
||
<div class="fields">
|
||
<div class="field"><label for="W">Short side, mm</label><input type="number" id="W" value="80" min="50" max="200"></div>
|
||
<div class="field"><label for="L">Long side, mm</label><input type="number" id="L" value="150" min="80" max="300"></div>
|
||
<div class="field"><label for="H">Box height, mm</label><input type="number" id="H" value="40" min="20" max="120"></div>
|
||
<div class="field"><label for="E">Lip / wall margin, mm</label><input type="number" id="E" value="3" min="0" max="15"></div>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Parts</h2>
|
||
<div class="fields">
|
||
<div class="field"><label for="BH">Track button hole Ø</label><input type="number" id="BH" value="12" min="6" max="24"></div>
|
||
<div class="field"><label for="BN">Its nut / cap Ø</label><input type="number" id="BN" value="15" min="6" max="30"></div>
|
||
<div class="field"><label for="SH">SEED hole Ø</label><input type="number" id="SH" value="16" min="6" max="30"></div>
|
||
<div class="field"><label for="SN">SEED nut / cap Ø</label><input type="number" id="SN" value="20" min="8" max="36"></div>
|
||
<div class="field"><label for="K">Knob Ø (pots, encoder)</label><input type="number" id="K" value="17" min="8" max="35"></div>
|
||
<div class="field"><label for="D">MIDI jack hole Ø (3.5 mm TRS)</label><input type="number" id="D" value="6" min="4" max="22" step="0.5"></div>
|
||
</div>
|
||
<p class="note">Measure your actual buttons and knobs. The nut or cap diameter is what sets how close things can sit.</p>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Fit check</h2>
|
||
<div class="warns" id="warns"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<section style="display:grid;gap:10px">
|
||
<h2>Drill list</h2>
|
||
<p class="note">Lid coordinates from the top-left corner as drawn (the cable wall is at the top). Wall coordinates: x from the left as drawn, height down from the lid edge or up from the inside floor.</p>
|
||
<div class="tbl"><table><thead><tr><th>Where</th><th>Part</th><th>x mm</th><th>y mm</th><th>Hole</th></tr></thead><tbody id="drill"></tbody></table></div>
|
||
</section>
|
||
|
||
<section class="cols">
|
||
<div class="card">
|
||
<h2>Why this arrangement</h2>
|
||
<ul id="why"></ul>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Before drilling</h2>
|
||
<ul>
|
||
<li>Check the inside, not just the lid: pot bodies (~17 mm) and button bodies hang down from the lid, and the socketed Pico stands about 13 mm off the stripboard. Under SEED sit R4 (flat) and pads P3/P4: keep those wires low, and check the SEED button's depth.</li>
|
||
<li>The stripboard lies on the floor with strip A against the back wall. The Pico's USB socket is about 41 mm from the board's left edge and about 16 mm above the inside floor (foam tape, board, female header, the Pico's header spacer): measure your stack and adjust the slot before cutting it. The slot has to clear the plug's moulding, not just the metal tip.</li><li>MIDI jacks: 3.5 mm stereo panel jacks, a 6 mm hole and a nut each.</li>
|
||
<li>The screen's wire hole goes under the module's 8-pin header, so the module hides it.</li>
|
||
<li>Centre-punch every mark and step-drill the big holes (a step bit is kinder to plastic too: go slowly so it doesn't crack).</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<script>
|
||
(function(){
|
||
var NS='http://www.w3.org/2000/svg', svg=document.getElementById('svg');
|
||
var ids=['W','L','H','E','BH','BN','SH','SN','K','D'];
|
||
try{ var saved=JSON.parse(localStorage.getItem('seedbox.panel')||'{}'); ids.forEach(function(k){ if(saved[k]!=null) document.getElementById(k).value=saved[k]; }); }catch(e){}
|
||
function v(id){ var x=parseFloat(document.getElementById(id).value); return isFinite(x)?x:0; }
|
||
function css(n){ return getComputedStyle(document.documentElement).getPropertyValue(n).trim(); }
|
||
function el(tag,attrs,parent,txt){ var e=document.createElementNS(NS,tag); for(var k in attrs) e.setAttribute(k,attrs[k]); if(txt!=null) e.textContent=txt; (parent||svg).appendChild(e); return e; }
|
||
function r1(x){ return Math.round(x*10)/10; }
|
||
|
||
var ORIENT='L';
|
||
var NAMES=[['KICK','KICK 2','SNARE','RUN'],['HAT','HAT 16','PERC','GHOST'],['BASS','MEL A','MEL B','PAD']]; // RUN = the run/stop toggle, in the grid but not in the matrix
|
||
function buttons(items,cx,cy,px,py,P){ for(var r=0;r<3;r++) for(var c=0;c<4;c++) { var nm=NAMES[r][c], x=cx+(c-1.5)*px, y=cy+(r-1)*py; items.push(nm==='RUN'?{id:'run',name:'RUN toggle',x:x,y:y,hole:6,clr:6,kind:'toggle'}:{id:'b'+r+c,name:nm,x:x,y:y,hole:P.BH,clr:P.BN/2,kind:'btn'}); } }
|
||
/* Portrait: lid W wide x L deep, jacks on the far short wall, USB on the side wall. */
|
||
function layoutP(W,L,H,E,P){
|
||
var m=E+3, items=[], scr={x:m,y:m,w:44,h:44}, colX=(scr.x+scr.w+W-m)/2;
|
||
items.push({name:'Encoder',x:colX,y:m+11,hole:7,clr:P.K/2,kind:'knob'});
|
||
var rowY=scr.y+scr.h+P.K/2+5, slot=function(i){ return m+(W-2*m)*(i+0.5)/3; };
|
||
items.push({name:'ENERGY pot',x:slot(0),y:rowY,hole:7,clr:P.K/2,kind:'knob'});
|
||
items.push({name:'DRIFT pot',x:slot(1),y:rowY,hole:7,clr:P.K/2,kind:'knob'});
|
||
items.push({name:'SEED',x:slot(2),y:rowY,hole:P.SH,clr:P.SN/2,kind:'seed'});
|
||
var bc=P.BN/2, px=Math.min(20,(W-2*m-2*bc)/3), lastY=L-m-bc-4, py=Math.max(px,Math.min(20,(lastY-(rowY+P.K/2+4+bc))/2));
|
||
buttons(items,W/2,lastY-py,px,py,P);
|
||
var jacks=[], jn=['MIDI IN','OUT · KIT','OUT · SYNTH'];
|
||
for(var j=0;j<3;j++) jacks.push({name:jn[j],x:slot(j),z:H/2,hole:P.D,clr:Math.max(5,P.D/2+2)});
|
||
return {A:W,B:L,m:m,scr:scr,items:items,jacks:jacks,usbBack:null,usbSide:{y:m+22,z:13,w:12,h:7},wire:{x:scr.x+5,y:scr.y+22,hole:8},px:px,py:py,
|
||
why:['The 12 track buttons are a 4 × 3 block, the same shape as the wiring matrix and the screen map. Synth tracks on the row nearest you.',
|
||
'Screen and encoder at the far end; knobs and SEED in a middle row. Your hands work the buttons without covering the screen.',
|
||
'Cables leave from the far short wall; USB on the side.',
|
||
'Narrow and deep: fits beside other gear on a crowded desk or pedalboard.']};
|
||
}
|
||
/* Landscape: lid L wide x W deep, three zones left to right, jacks and USB on the back long wall. */
|
||
function layoutL(W,L,H,E,P){
|
||
var A=L, B=W, m=E+3, items=[], scr={x:m,y:m,w:44,h:44};
|
||
var encY=scr.y+scr.h+P.K/2+3;
|
||
items.push({name:'Encoder',x:scr.x+P.K/2+1,y:encY,hole:7,clr:P.K/2,kind:'knob'});
|
||
var bc=P.BN/2, px=Math.min(20,(A-m-(scr.x+scr.w)-Math.max(P.SN,P.K)-6-2*bc)/3), py=Math.min(20,(B-2*m-2*bc)/2);
|
||
var bx=A-m-bc-1.5*px, by=B/2;
|
||
buttons(items,bx,by,px,py,P);
|
||
var midL=scr.x+scr.w, midR=bx-1.5*px-bc, cx=(midL+midR)/2, ys=function(i){ return m+(B-2*m)*(i+0.5)/3; };
|
||
items.push({name:'ENERGY pot',x:cx,y:ys(0),hole:7,clr:P.K/2,kind:'knob'});
|
||
items.push({name:'DRIFT pot',x:cx,y:ys(1),hole:7,clr:P.K/2,kind:'knob'});
|
||
items.push({name:'SEED',x:cx,y:ys(2),hole:P.SH,clr:P.SN/2,kind:'seed'});
|
||
var jacks=[], jn=['MIDI IN','OUT · KIT','OUT · SYNTH'];
|
||
var jc=Math.max(5,P.D/2+2), jp=Math.max(16,2*jc+4);
|
||
for(var j=0;j<3;j++) jacks.push({name:jn[j],x:A-m-jc-2-(2-j)*jp,z:H/2,hole:P.D,clr:jc});
|
||
return {A:A,B:B,m:m,scr:scr,items:items,jacks:jacks,usbBack:{x:m+38,z:16,w:12,h:8},usbSide:null,wire:{x:scr.x+5,y:scr.y+22,hole:8},px:px,py:py,
|
||
why:['Three zones left to right: screen with the encoder, then ENERGY / DRIFT / SEED in a column, then the 4 × 3 block: 11 track buttons with the RUN toggle in the top-right corner.',
|
||
'The button block has the same shape as the wiring matrix and the screen map, so the LCD map sits right next to the buttons it mirrors.',
|
||
'Everything is one hand-reach from the front edge, and the screen is never under your hands.',
|
||
'All cables and USB leave from the back long wall. Wider and shallower: sits in front of a keyboard or sampler.']};
|
||
}
|
||
function layout(W,L,H,E,P){ return ORIENT==='L' ? layoutL(W,L,H,E,P) : layoutP(W,L,H,E,P); }
|
||
|
||
function check(W,L,H,E,Lo){
|
||
W=Lo.A; L=Lo.B;
|
||
var out=[], it=Lo.items, i, j;
|
||
function inRect(a,R,pad){ var dx=Math.max(R.x-a.x,0,a.x-(R.x+R.w)), dy=Math.max(R.y-a.y,0,a.y-(R.y+R.h)); return Math.hypot(dx,dy)<a.clr+pad; }
|
||
for(i=0;i<it.length;i++){
|
||
var a=it[i];
|
||
if(a.x-a.clr<E || a.x+a.clr>W-E || a.y-a.clr<E || a.y+a.clr>L-E){ a.bad=true; out.push(a.name+' runs past the usable lid area.'); }
|
||
if(inRect(a,Lo.scr,1.5)){ a.bad=true; out.push(a.name+' overlaps the screen module.'); }
|
||
for(j=i+1;j<it.length;j++){ var b=it[j], d=Math.hypot(a.x-b.x,a.y-b.y);
|
||
if(d<a.clr+b.clr+1){ a.bad=b.bad=true; out.push(a.name+' and '+b.name+' are too close ('+r1(d)+' mm apart, need '+r1(a.clr+b.clr+1)+').'); } }
|
||
}
|
||
var J=Lo.jacks;
|
||
for(i=0;i<J.length;i++){ if(J[i].z-J[i].clr<E || J[i].z+J[i].clr>H-E){ J[i].bad=true; out.push('The box is too shallow for the MIDI jacks (they need about '+r1(2*J[i].clr+2*E)+' mm of wall height).'); break; } }
|
||
if(Lo.usbBack && J[0].x-12<Lo.usbBack.x+Lo.usbBack.w/2){ out.push('The USB slot runs into the MIDI IN jack.'); }
|
||
for(i=0;i<J.length-1;i++) if(J[i+1].x-J[i].x<J[i].clr+J[i+1].clr+2){ J[i].bad=J[i+1].bad=true; out.push('MIDI jacks are too close; their nuts will clash.'); break; }
|
||
if(ORIENT==='P') out.push('Portrait does not match the stripboard: its USB edge needs the long back wall. Use Landscape for this build.');
|
||
if(Lo.px<15) out.push('Button pitch is '+r1(Lo.px)+' mm, tight for fingers. 16 mm or more plays better.');
|
||
return out;
|
||
}
|
||
|
||
function draw(){
|
||
var W=v('W'), L=v('L'), H=v('H'), E=v('E'), P={BH:v('BH'),BN:v('BN'),SH:v('SH'),SN:v('SN'),K:v('K'),D:v('D')};
|
||
try{ var s={}; ids.forEach(function(k){ s[k]=document.getElementById(k).value; }); localStorage.setItem('seedbox.panel',JSON.stringify(s)); }catch(e){}
|
||
var Lo=layout(W,L,H,E,P), warns=check(W,L,H,E,Lo);
|
||
var C={ink:css('--ink'),muted:css('--muted'),line:css('--line'),accent:css('--accent'),warn:css('--warn'),metal:css('--metal'),edge:css('--metal-edge'),print:css('--print'),pcb:css('--pcb'),lcd:css('--lcd'),font:css('--f-mono')};
|
||
svg.innerHTML='';
|
||
var LW=Lo.A, LD=Lo.B, pad=14, gap=8, ox=pad+12, oyEnd=pad+8, oyLid=oyEnd+H+gap, oxSide=ox+LW+gap, VW=oxSide+(Lo.usbSide?H:0)+pad+6, VH=oyLid+LD+pad+10;
|
||
svg.setAttribute('viewBox','0 0 '+VW+' '+VH);
|
||
var g=el('g',{'font-family':C.font,'font-size':'2.6'});
|
||
function panel(x,y,w,h,label){ el('rect',{x:x,y:y,width:w,height:h,rx:1.5,fill:C.metal,stroke:C.edge,'stroke-width':0.5},g); el('text',{x:x,y:y-2.2,fill:C.muted,'font-size':'2.8'},g,label); }
|
||
function dim(x1,y1,x2,y2,txt,vert){
|
||
el('line',{x1:x1,y1:y1,x2:x2,y2:y2,stroke:C.muted,'stroke-width':0.25},g);
|
||
[[x1,y1],[x2,y2]].forEach(function(p){ var t=vert?{x1:p[0]-1.5,y1:p[1],x2:p[0]+1.5,y2:p[1]}:{x1:p[0],y1:p[1]-1.5,x2:p[0],y2:p[1]+1.5}; t.stroke=C.muted; t['stroke-width']=0.25; el('line',t,g); });
|
||
var t=el('text',{x:(x1+x2)/2+(vert?-1.6:0),y:(y1+y2)/2+(vert?0:-1.2),fill:C.muted,'text-anchor':'middle','font-size':'2.6'},g,txt);
|
||
if(vert) t.setAttribute('transform','rotate(-90 '+((x1+x2)/2-1.6)+' '+((y1+y2)/2)+')');
|
||
}
|
||
function hole(cx,cy,d,clr,bad,kind,label,below){
|
||
if(kind==='knob'){ el('circle',{cx:cx,cy:cy,r:clr,fill:'#15181C',stroke:bad?C.warn:'#4A525B','stroke-width':bad?0.6:0.4},g); el('line',{x1:cx,y1:cy-clr+1.2,x2:cx,y2:cy-clr*0.35,stroke:'#D9DEE3','stroke-width':0.8},g); }
|
||
else if(kind==='toggle'){ el('circle',{cx:cx,cy:cy,r:clr,fill:'#2C3238',stroke:bad?C.warn:'#4A525B','stroke-width':0.4},g); el('rect',{x:cx-1,y:cy-6,width:2,height:6,rx:1,fill:'#C9CFD5'},g); }
|
||
else { el('circle',{cx:cx,cy:cy,r:clr,fill:'#2C3238',stroke:bad?C.warn:'#4A525B','stroke-width':bad?0.6:0.4},g); el('circle',{cx:cx,cy:cy,r:d/2-0.6,fill:kind==='seed'?'#3B4148':'#30363D',stroke:'#14171A','stroke-width':0.3},g); }
|
||
el('circle',{cx:cx,cy:cy,r:0.5,fill:C.accent},g);
|
||
if(label) el('text',{x:cx,y:below?cy+clr+3:cy-clr-1.2,fill:C.print,'text-anchor':'middle','font-size':'2.4','letter-spacing':'0.1'},g,label);
|
||
}
|
||
// end wall (jacks)
|
||
panel(ox,oyEnd,LW,H,ORIENT==='L'?'BACK WALL · cables':'END WALL · cables');
|
||
Lo.jacks.forEach(function(j){ var cx=ox+j.x, cy=oyEnd+j.z;
|
||
hole(cx,cy,j.hole,j.clr,j.bad,'jack'); el('text',{x:cx,y:oyEnd+H-2,fill:C.print,'text-anchor':'middle','font-size':'2.2'},g,j.name); });
|
||
// lid
|
||
panel(ox,oyLid,LW,LD,'LID · top view · you sit at the bottom edge');
|
||
el('rect',{x:ox+E,y:oyLid+E,width:LW-2*E,height:LD-2*E,fill:'none',stroke:'#3A4047','stroke-width':0.3,'stroke-dasharray':'1.2 1'},g);
|
||
var S=Lo.scr, sx=ox+S.x, sy=oyLid+S.y;
|
||
el('rect',{x:sx,y:sy,width:S.w,height:S.h,rx:1,fill:C.pcb},g);
|
||
[[2.5,2.5],[S.w-2.5,2.5],[2.5,S.h-2.5],[S.w-2.5,S.h-2.5]].forEach(function(p){ el('circle',{cx:sx+p[0],cy:sy+p[1],r:1.3,fill:'#7A1416'},g); });
|
||
el('rect',{x:sx+5,y:sy+2.5,width:33,height:36,rx:0.8,fill:'#A9B0B6'},g);
|
||
el('rect',{x:sx+7,y:sy+5,width:26,height:30,rx:0.6,fill:C.lcd},g);
|
||
for(var p=0;p<8;p++) el('circle',{cx:sx+2.5,cy:sy+12+p*2.54,r:0.55,fill:'#E9C46A'},g);
|
||
// a hint of the 4x3 track map on the LCD
|
||
for(var r=0;r<3;r++) for(var c=0;c<4;c++) el('rect',{x:sx+8.5+c*6,y:sy+20+r*4.6,width:5,height:3.6,fill:(r+c)%3?'#2A3330':'none',stroke:'#2A3330','stroke-width':0.3},g);
|
||
el('circle',{cx:ox+Lo.wire.x,cy:oyLid+Lo.wire.y,r:Lo.wire.hole/2,fill:'none',stroke:C.accent,'stroke-width':0.35,'stroke-dasharray':'0.8 0.6'},g);
|
||
Lo.items.forEach(function(it){
|
||
var lab=it.kind==='knob'||it.kind==='seed'||it.kind==='toggle'?it.name.replace(' pot','').replace(' toggle',''):it.name;
|
||
hole(ox+it.x,oyLid+it.y,it.hole,it.clr,it.bad,it.kind==='fill'?'btn':it.kind,lab,true);
|
||
});
|
||
// USB slot: side wall (portrait) or back wall (landscape)
|
||
if(Lo.usbSide){
|
||
panel(oxSide,oyLid,H,LD,'SIDE');
|
||
var U=Lo.usbSide; el('rect',{x:oxSide+H-U.z-U.h/2,y:oyLid+U.y-U.w/2,width:U.h,height:U.w,rx:1,fill:'#0A0C0E',stroke:C.accent,'stroke-width':0.35},g);
|
||
el('text',{x:oxSide+H/2,y:oyLid+U.y+U.w/2+4,fill:C.print,'text-anchor':'middle','font-size':'2.4'},g,'USB');
|
||
el('text',{x:oxSide+H/2,y:oyLid+LD-3,fill:C.muted,'text-anchor':'middle','font-size':'2.2'},g,'floor →');
|
||
} else {
|
||
var Ub=Lo.usbBack; el('rect',{x:ox+Ub.x-Ub.w/2,y:oyEnd+H-Ub.z-Ub.h/2,width:Ub.w,height:Ub.h,rx:1,fill:'#0A0C0E',stroke:C.accent,'stroke-width':0.35},g);
|
||
el('text',{x:ox+Ub.x,y:oyEnd+H-2,fill:C.print,'text-anchor':'middle','font-size':'2.2'},g,'USB');
|
||
}
|
||
// dimensions
|
||
dim(ox,oyLid+LD+6,ox+LW,oyLid+LD+6,LW+' mm');
|
||
dim(ox-6,oyLid,ox-6,oyLid+LD,LD+' mm',true);
|
||
dim(ox-6,oyEnd,ox-6,oyEnd+H,H+' mm',true);
|
||
// fit check
|
||
var wb=document.getElementById('warns'); wb.innerHTML='';
|
||
if(!warns.length){ var d0=document.createElement('div'); d0.className='ok'; d0.textContent='Everything fits. Button pitch '+r1(Lo.px)+' × '+r1(Lo.py)+' mm.'; wb.appendChild(d0); }
|
||
warns.filter(function(w,i,a){ return a.indexOf(w)===i; }).forEach(function(w){ var d=document.createElement('div'); d.className='bad'; d.textContent=w; wb.appendChild(d); });
|
||
// drill table
|
||
var tb=document.getElementById('drill'); tb.innerHTML='';
|
||
function row(where,part,x,y,h){ var tr=document.createElement('tr'); [where,part,x,y,h].forEach(function(t,i){ var td=document.createElement('td'); if(i>=2) td.className='n'; td.textContent=t; tr.appendChild(td); }); tb.appendChild(tr); }
|
||
row('Lid','Screen wire hole (under the header)',r1(Lo.wire.x),r1(Lo.wire.y),'Ø '+Lo.wire.hole);
|
||
Lo.items.forEach(function(it){ row('Lid',it.name,r1(it.x),r1(it.y),'Ø '+it.hole); });
|
||
var wn=ORIENT==='L'?'Back wall':'End wall';
|
||
Lo.jacks.forEach(function(j){ row(wn,'Jack '+j.name,r1(j.x),r1(j.z)+' down','Ø '+j.hole); });
|
||
if(Lo.usbSide) row('Side wall','USB slot (Pico port)',r1(Lo.usbSide.z)+' up from inside floor',r1(Lo.usbSide.y),Lo.usbSide.w+' × '+Lo.usbSide.h+' slot');
|
||
else row(wn,'USB slot (Pico port)',r1(Lo.usbBack.x),r1(Lo.usbBack.z)+' up from inside floor',Lo.usbBack.w+' × '+Lo.usbBack.h+' slot');
|
||
var wy=document.getElementById('why'); wy.innerHTML=''; Lo.why.forEach(function(t){ var li=document.createElement('li'); li.textContent=t; wy.appendChild(li); });
|
||
document.getElementById('oP').setAttribute('aria-pressed',String(ORIENT==='P')); document.getElementById('oL').setAttribute('aria-pressed',String(ORIENT==='L'));
|
||
}
|
||
ids.forEach(function(k){ document.getElementById(k).addEventListener('input',draw); });
|
||
try{ ORIENT=localStorage.getItem('seedbox.orient')==='P'?'P':'L'; }catch(e){}
|
||
[['oP','P'],['oL','L']].forEach(function(b){ document.getElementById(b[0]).addEventListener('click',function(){ ORIENT=b[1]; try{ localStorage.setItem('seedbox.orient',ORIENT); }catch(e){} draw(); }); });
|
||
draw();
|
||
})();
|
||
</script>
|