master
Ray source: sync the page's src/ with the 2.7.1 the binary was built from 85bdcb7 2mo ago
<!doctype html><html lang=en><head><meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1"><title>__TITLE__</title>
<style>
:root{--bg:#0a0e14;--panel:#111722;--panel2:#161d2b;--edge:#232c3d;--ink:#e6edf3;--mut:#9fb0c4;--mut2:#6b7a90;
  --acc:#00e6dc;--blue:#39b8ff;--purple:#8c6bf7;--hi:#00e6dc;
  --accent:linear-gradient(100deg,#00e6dc,#39b8ff 60%,#8c6bf7)}
*{box-sizing:border-box;margin:0}html,body{height:100%}
body{background:
    radial-gradient(1100px 700px at 88% -10%, rgba(0,184,177,.10), transparent 60%),
    radial-gradient(900px 700px at -5% 110%, rgba(140,107,247,.09), transparent 55%),
    var(--bg);
  color:var(--ink);font-family:'Satoshi',system-ui,-apple-system,'Segoe UI',sans-serif;
  -webkit-font-smoothing:antialiased;display:flex;flex-direction:column;overflow:hidden}
.wrap{height:100%;display:flex;flex-direction:column;padding:14px 18px 12px}
header{display:flex;align-items:baseline;gap:12px;margin-bottom:10px;flex:none}
header h1{font-size:17px;font-weight:750;letter-spacing:-.3px}
header .accent{background:var(--accent);-webkit-background-clip:text;background-clip:text;color:transparent}
header .lay{margin-left:auto;font:600 10.5px 'JetBrains Mono',ui-monospace,monospace;color:var(--mut2);
  text-transform:uppercase;letter-spacing:.1em;border:1px solid var(--edge);border-radius:6px;padding:3px 9px}
.stage{flex:1;min-height:0;display:grid;gap:13px}
.pane{background:var(--panel);border:1px solid var(--edge);border-radius:14px;display:flex;flex-direction:column;
  min-height:0;min-width:0;overflow:hidden;position:relative;transition:border-color .18s,box-shadow .18s}
.pane.lit{border-color:rgba(0,230,220,.4);box-shadow:0 0 0 1px rgba(0,230,220,.18),0 10px 40px rgba(0,0,0,.4)}
.pane h2{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--mut2);font-weight:600;
  padding:11px 13px 8px;display:flex;align-items:center;gap:8px;flex:none}
.pane h2 .k{font:600 10px 'JetBrains Mono',ui-monospace,monospace;color:var(--mut);
  border:1px solid var(--edge);border-radius:5px;padding:1px 6px}
.pane h2 .ctl{margin-left:auto;display:flex;gap:5px;flex-wrap:wrap;justify-content:flex-end}
.pane .body{flex:1;min-height:0;min-width:0;position:relative}
.svgstage{position:absolute;inset:0;overflow:hidden;cursor:grab;touch-action:none}
.svgstage.drag{cursor:grabbing}
.svgstage svg{position:absolute;inset:0;width:100%;height:100%;display:block}
.v3d{position:absolute;inset:0}
.v3d canvas{width:100%;height:100%;display:block;outline:none;touch-action:none}
.ph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--mut2);font-size:12px}

/* shared toggle/button chrome */
.tog{display:inline-flex;align-items:center;gap:6px;font:600 10.5px 'JetBrains Mono',ui-monospace,monospace;color:var(--mut2);
  background:var(--panel2);border:1px solid var(--edge);border-radius:999px;padding:3px 9px;cursor:pointer;user-select:none}
.tog.on{color:var(--ink);border-color:#3a4b66}.tog:not(.on){opacity:.55}
.tog .d{width:8px;height:8px;border-radius:50%;background:#485162}
.btn{font:600 10px 'JetBrains Mono',ui-monospace,monospace;letter-spacing:.03em;color:var(--mut2);background:var(--panel2);
  border:1px solid var(--edge);border-radius:6px;padding:3px 8px;cursor:pointer;transition:color .12s,border-color .12s}
.btn:hover{color:var(--ink);border-color:rgba(0,230,220,.4)}.btn.on{color:var(--acc);border-color:rgba(0,230,220,.5)}
.btn:disabled{opacity:.5;cursor:default}

/* SVG highlight (data-num based, adom-lbr convention) */
.pingroup,.pad{cursor:pointer}
[data-num].hi .pinvis,.pingroup.hi .pinvis{stroke:var(--hi)!important;stroke-width:7}
.pingroup.hi text,.pingroup.hi .pin-name,.pingroup.hi .pin-num{fill:var(--hi)!important}
rect.pad.hi{stroke:var(--hi);stroke-width:.2;paint-order:stroke;filter:drop-shadow(0 0 5px rgba(0,230,220,.7))}
.lyr-off{display:none}

/* pin-map pane */
.pins{position:absolute;inset:0;display:flex;flex-direction:column;min-height:0}
.pinscroll{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:3px 6px 6px;
  scrollbar-width:thin;scrollbar-color:#3a4658 transparent}
.pinscroll::-webkit-scrollbar{width:9px}.pinscroll::-webkit-scrollbar-track{background:transparent}
.pinscroll::-webkit-scrollbar-thumb{background:#3a4658;border-radius:6px;border:2px solid transparent;background-clip:content-box}
.pinscroll::-webkit-scrollbar-thumb:hover{background:#4c5b71;background-clip:content-box}
.pinscroll::-webkit-scrollbar-button{display:none;height:0;width:0}
.prow-head,.prow{display:grid;align-items:center;gap:11px;padding:0 12px;height:36px;border-radius:7px}
.prow-head{position:sticky;top:0;z-index:1;background:linear-gradient(var(--panel),var(--panel) 72%,rgba(17,23,34,.55));
  color:var(--mut2);font:600 9.5px 'JetBrains Mono',monospace;letter-spacing:.1em;text-transform:uppercase;height:27px}
.prow{cursor:pointer;transition:background .1s}
.prow:hover{background:rgba(255,255,255,.04)}
.prow.hi{background:rgba(0,230,220,.12);box-shadow:inset 2px 0 0 var(--acc)}
.prow.off{display:none}
.pdot{width:9px;height:9px;border-radius:50%;justify-self:center;box-shadow:inset 0 0 0 1px rgba(0,0,0,.35)}
.prow.hi .pdot{box-shadow:0 0 7px 1px currentColor}
.prow .pn{font:600 12px 'JetBrains Mono',monospace;color:var(--mut2);text-align:right}
.prow.hi .pn{color:var(--acc)}
.prow .pnm{font:600 12.5px 'Satoshi',sans-serif;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.prow .pdesc{min-width:0;font:500 11.5px 'Satoshi',sans-serif;color:var(--mut2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.prow.hi .pdesc{color:var(--mut)}
.prow .ptype{font:600 10px 'JetBrains Mono',monospace;color:#06121a;padding:1px 7px;border-radius:5px;white-space:nowrap;justify-self:end}
.pinfilter{width:74px;background:var(--panel2);border:1px solid var(--edge);border-radius:6px;color:var(--ink);
  font:500 11px 'JetBrains Mono',ui-monospace,monospace;padding:3px 8px;outline:none;transition:border-color .12s,width .18s}
.pinfilter:focus{border-color:rgba(0,230,220,.45);width:104px}.pinfilter::placeholder{color:var(--mut2)}
.pinempty{color:var(--mut2);font:12px 'JetBrains Mono',monospace;padding:22px 12px;text-align:center}
.pinfoot{flex:none;border-top:1px solid var(--edge);padding:10px 13px 12px;background:rgba(0,0,0,.14)}
.pinstat{font:600 10.5px 'JetBrains Mono',monospace;color:var(--mut2);margin-bottom:9px}
.pinstat b{color:var(--ink);font-weight:700}.pinstat .lbl{text-transform:uppercase;letter-spacing:.1em;opacity:.75;margin-right:9px}
.typechips{display:flex;flex-wrap:wrap;gap:6px}
.typechip{display:inline-flex;align-items:center;gap:6px;cursor:pointer;user-select:none;
  font:600 10.5px 'JetBrains Mono',ui-monospace,monospace;color:var(--mut);
  background:color-mix(in srgb,var(--tc) 13%,transparent);border:1px solid color-mix(in srgb,var(--tc) 32%,transparent);
  border-radius:7px;padding:3px 8px;transition:background .12s,border-color .12s,color .12s}
.typechip .sw{width:8px;height:8px;border-radius:2px;background:var(--tc)}
.typechip .c{color:var(--mut2);font-weight:700}
.typechip:hover,.typechip.on{color:var(--ink);background:color-mix(in srgb,var(--tc) 26%,transparent);border-color:var(--tc)}
.readout{position:absolute;left:12px;bottom:11px;background:rgba(10,16,24,.85);border:1px solid var(--edge);
  border-radius:9px;padding:7px 11px;backdrop-filter:blur(7px);min-width:130px;font:600 12px 'JetBrains Mono',monospace;
  color:var(--mut);pointer-events:none}
.readout b{color:var(--acc)}.readout .rt{margin-left:8px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--mut2)}
.readout.empty{color:var(--mut2);font-weight:500;font-size:11px}
</style></head><body>
<div class=wrap>
  <header>
    <h1>Adom <span class=accent>Library</span> <span id=hsub></span></h1>
    <span class=lay id=laytag></span>
  </header>
  <div class=stage id=stage></div>
</div>

<!-- data blobs (inert until JS wires them) -->
<script id=cfg type="application/json">__CONFIG__</script>
<template id=tpl-symbol>__SYM_SVG__</template>
<template id=tpl-footprint>__FP_SVG__</template>
<template id=tpl-layerbar>__LAYERBAR__</template>

<script>__VIEWERJS_INLINE__</script>
<script>
"use strict";
var CFG = JSON.parse(document.getElementById('cfg').textContent);
var PINS = CFG.pins.rows || [];
var GLB = CFG.glb || "", MARKS = CFG.marks || [];
document.getElementById('hsub').textContent = '· ' + (CFG.panes.join(' · '));
document.getElementById('laytag').textContent = CFG.layout;
document.title = CFG.mpn + ' — ' + CFG.layout;

var byNum = {}; PINS.forEach(function(p){ byNum[String(p.number)] = p; });
var stage = document.getElementById('stage');

var PANE_META = {
  symbol:    {title:'Symbol',    k:'.kicad_sym'},
  footprint: {title:'Footprint', k:'.kicad_mod'},
  '3d':      {title:'3D model',  k:'chip.glb'},
  pins:      {title:'Pin map',   k:''},
};

/* ---------- layout ---------- */
function applyLayout(){
  var panes = CFG.panes, n = panes.length;
  if (CFG.layout === 'columns') {
    stage.style.gridTemplateRows = '1fr';
    stage.style.gridTemplateColumns = panes.map(function(p){
      if (p === 'pins') return CFG.pins.description ? '300px' : '234px';   // narrow far-right column
      if (p === '3d') return '1.3fr';
      return '1fr';
    }).join(' ');
  } else if (CFG.layout === 'single') {
    stage.style.gridTemplateColumns = '1fr';
    stage.style.gridTemplateRows = 'repeat(' + n + ',1fr)';
  } else { /* quad */
    stage.style.gridTemplateColumns = n > 1 ? '1fr 1fr' : '1fr';
    stage.style.gridTemplateRows = n > 2 ? '1fr 1fr' : '1fr';
  }
}

function mkPane(id){
  var meta = PANE_META[id] || {title:id, k:''};
  var pane = document.createElement('div'); pane.className = 'pane'; pane.id = 'pane-' + id;
  var h = document.createElement('h2');
  h.innerHTML = meta.title + (meta.k ? ' <span class="k">' + meta.k + '</span>' : '');
  var ctl = document.createElement('span'); ctl.className = 'ctl'; h.appendChild(ctl);
  var body = document.createElement('div'); body.className = 'body';
  pane.appendChild(h); pane.appendChild(body); stage.appendChild(pane);
  return {pane:pane, ctl:ctl, body:body};
}

/* ---------- cross-highlight bus (one page → direct DOM; + BroadcastChannel for external embed) ---------- */
var SELF = 'c' + Math.random().toString(36).slice(2);
var BUS = ('BroadcastChannel' in window) ? new BroadcastChannel('lbr-hover') : null;
function els(n){ return [].slice.call(document.querySelectorAll('[data-num="' + cssq(n) + '"]')); }
function cssq(s){ return String(s).replace(/["\\]/g, '\\$&'); }
function clearHi(){ [].slice.call(document.querySelectorAll('.hi')).forEach(function(e){ e.classList.remove('hi'); }); }
function paneLit(on){ ['symbol','footprint','3d','pins'].forEach(function(id){ var p=document.getElementById('pane-'+id); if(p)p.classList.toggle('lit', !!on); }); }
function apply(n){
  clearHi();
  if (n != null && n !== '') {
    els(n).forEach(function(e){ e.classList.add('hi'); });
    var r = document.querySelector('.prow[data-num="' + cssq(n) + '"]');
    if (r && r.scrollIntoView) { try { r.scrollIntoView({block:'nearest'}); } catch(_){} }
  }
  if (window.__lbr3dHighlight) window.__lbr3dHighlight(n == null ? null : n);
  updateReadouts(n);
  paneLit(n != null && n !== '');
}
function post(n){ var m={type:'lbr-hover',pin:(n==null||n==='')?null:String(n),mpn:CFG.mpn,src:SELF};
  try{ BUS && BUS.postMessage(m); }catch(_){}
  try{ if(window.parent && window.parent!==window) window.parent.postMessage(m,'*'); }catch(_){} }
function setPin(n){ apply(n); post(n); }
if (BUS) BUS.onmessage = function(e){ var d=e.data||{}; if(d.type!=='lbr-hover'||d.src===SELF) return; apply(d.pin); };
window.addEventListener('message', function(e){ var d=e.data||{}; if(d.type!=='lbr-hover'||d.src===SELF) return; apply(d.pin); });

var readouts = [];
function updateReadouts(n){
  var p = n==null?null:byNum[String(n)];
  readouts.forEach(function(r){
    if (!p) { r.className='readout empty'; r.textContent='hover a pin'; return; }
    r.className='readout';
    r.innerHTML='<b>PIN '+esc(p.number)+'</b> '+esc(p.name)+(p.type?'<span class="rt">'+esc(p.type)+'</span>':'');
  });
}
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];}); }

function wireHover(root){
  [].slice.call(root.querySelectorAll('[data-num]')).forEach(function(g){
    var n = g.getAttribute('data-num');
    g.addEventListener('mouseenter', function(){ setPin(n); });
    g.addEventListener('mouseleave', function(){ setPin(null); });
  });
}

/* ---------- SVG pane (symbol / footprint): inject + pan/zoom + hover + (footprint) layers ---------- */
function buildSvgPane(id, tplId, withLayers, ctl, body){
  var stg = document.createElement('div'); stg.className='svgstage'; body.appendChild(stg);
  var ro = document.createElement('div'); ro.className='readout empty'; ro.textContent='hover a pin'; body.appendChild(ro); readouts.push(ro);
  var frag = document.getElementById(tplId).content.cloneNode(true);
  stg.appendChild(frag);
  var svg = stg.querySelector('svg');
  if (!svg) { stg.innerHTML = '<div class="ph">no '+id+' render</div>'; return; }
  wireHover(stg);
  attachPanZoom(stg, svg);
  if (withLayers) buildLayerToggles(ctl, stg);
}
function attachPanZoom(stg, svg){
  var vb = (svg.getAttribute('viewBox')||'0 0 100 100').split(/\s+/).map(Number), base = vb.slice();
  function setVB(){ svg.setAttribute('viewBox', vb.join(' ')); }
  stg.addEventListener('wheel', function(e){ e.preventDefault(); var r=stg.getBoundingClientRect();
    var mx=(e.clientX-r.left)/r.width, my=(e.clientY-r.top)/r.height, f=e.deltaY<0?0.88:1.14;
    var nw=vb[2]*f, nh=vb[3]*f; vb[0]+=(vb[2]-nw)*mx; vb[1]+=(vb[3]-nh)*my; vb[2]=nw; vb[3]=nh; setVB(); }, {passive:false});
  var pan=null;
  stg.addEventListener('pointerdown', function(e){ pan={x:e.clientX,y:e.clientY}; stg.classList.add('drag'); try{stg.setPointerCapture(e.pointerId);}catch(_){} });
  stg.addEventListener('pointermove', function(e){ if(!pan)return; var r=stg.getBoundingClientRect();
    vb[0]-=(e.clientX-pan.x)/r.width*vb[2]; vb[1]-=(e.clientY-pan.y)/r.height*vb[3]; pan={x:e.clientX,y:e.clientY}; setVB(); });
  function end(){ pan=null; stg.classList.remove('drag'); }
  stg.addEventListener('pointerup', end); stg.addEventListener('pointerleave', end);
  stg.addEventListener('dblclick', function(){ vb=base.slice(); setVB(); });
}
function buildLayerToggles(ctl, stg){
  var bar = document.getElementById('tpl-layerbar').content.cloneNode(true);
  var holder = document.createElement('span'); holder.appendChild(bar);
  [].slice.call(holder.querySelectorAll('.lyrtog')).forEach(function(b){
    b.classList.add('tog');
    function apply(on){ [].slice.call(stg.querySelectorAll('.ly-'+b.getAttribute('data-lyr'))).forEach(function(el){ el.classList.toggle('lyr-off', !on); }); }
    apply(b.classList.contains('on'));
    b.addEventListener('click', function(){ apply(b.classList.toggle('on')); });
    ctl.appendChild(b);
  });
}

/* ---------- pin-map pane ---------- */
function buildPinsPane(ctl, body){
  var wrap = document.createElement('div'); wrap.className='pins'; body.appendChild(wrap);
  var full = !!CFG.pins.description, rollup = !!CFG.pins.rollup;
  // header controls: count + filter
  var count = document.createElement('span'); count.className='k'; count.textContent = PINS.length + ' pins';
  var h2 = body.parentNode.querySelector('h2'); h2.insertBefore(count, ctl);
  var filter = document.createElement('input'); filter.className='pinfilter'; filter.type='text';
  filter.placeholder='filter'; filter.setAttribute('aria-label','filter pins'); filter.spellcheck=false; ctl.appendChild(filter);

  var scroll = document.createElement('div'); scroll.className='pinscroll'; wrap.appendChild(scroll);
  var head = document.createElement('div'); head.className='prow-head';
  var cols = full ? '15px 28px 74px minmax(0,1fr) auto' : '15px 30px 1fr auto';
  head.style.gridTemplateColumns = cols;
  head.innerHTML = full
    ? '<span></span><span style="text-align:right">#</span><span>Name</span><span>Description</span><span>Type</span>'
    : '<span></span><span style="text-align:right">#</span><span>Name</span><span>Type</span>';
  scroll.appendChild(head);

  var rows = [];
  PINS.forEach(function(p){
    var row = document.createElement('div'); row.className='prow'; row.setAttribute('data-num', p.number);
    row.style.gridTemplateColumns = cols;
    row.dataset.hay = ((p.name||'')+' '+(p.type||'')+' '+(p.desc||'')).toLowerCase();
    var col = p.color || '#5c6b82';
    var chip = '<span class="ptype" style="background:'+col+'">'+esc(p.type||'—')+'</span>';
    row.innerHTML = '<span class="pdot" style="background:'+col+';color:'+col+'"></span>'
      + '<span class="pn">'+esc(p.number)+'</span>'
      + '<span class="pnm" title="'+esc(p.name)+'">'+esc(p.name)+'</span>'
      + (full ? '<span class="pdesc" title="'+esc(p.desc||'')+'">'+esc(p.desc||'')+'</span>' : '')
      + chip;
    scroll.appendChild(row); rows.push(row);
    row.addEventListener('mouseenter', function(){ setPin(p.number); });
    row.addEventListener('mouseleave', function(){ setPin(null); });
  });

  // type roll-up footer (full/quad only) — the "earn its space" summary + type highlighter
  if (rollup) {
    var foot = document.createElement('div'); foot.className='pinfoot'; wrap.appendChild(foot);
    var typeMap = {}; var order = [];
    PINS.forEach(function(p){ var t=p.type||'—'; if(!typeMap[t]){typeMap[t]=[];order.push(t);} typeMap[t].push(p.number); });
    var stat = document.createElement('div'); stat.className='pinstat';
    stat.innerHTML = '<span class="lbl">types</span><b>'+PINS.length+'</b> pins &nbsp;·&nbsp; <b>'+order.length+'</b> types';
    foot.appendChild(stat);
    var chips = document.createElement('div'); chips.className='typechips'; var chipEls=[];
    order.forEach(function(t){ var nums=typeMap[t], col=(PINS.find(function(p){return (p.type||'—')===t;})||{}).color||'#5c6b82';
      var c=document.createElement('div'); c.className='typechip'; c.style.setProperty('--tc',col); c.dataset.nums=nums.join(',');
      c.innerHTML='<span class="sw"></span>'+esc(t)+'<span class="c">'+nums.length+'</span>';
      c.addEventListener('mouseenter', function(){ hiSet(nums); }); c.addEventListener('mouseleave', function(){ setPin(null); });
      chips.appendChild(c); chipEls.push(c); });
    foot.appendChild(chips);
    // light chips whose pins are active
    window.__pinChipSync = function(n){ chipEls.forEach(function(c){ c.classList.toggle('on', n!=null && c.dataset.nums.split(',').indexOf(String(n))>=0); }); };
  }

  // filter
  var empty=null;
  filter.addEventListener('input', function(){
    var q=filter.value.trim().toLowerCase(), shown=0;
    rows.forEach(function(r){ var ok=!q||(r.getAttribute('data-num')+' '+r.dataset.hay).indexOf(q)>=0; r.classList.toggle('off',!ok); if(ok)shown++; });
    count.textContent = q ? shown+' / '+PINS.length : PINS.length+' pins';
    if(!shown){ if(!empty){empty=document.createElement('div');empty.className='pinempty';scroll.appendChild(empty);} empty.textContent='no pins match "'+filter.value+'"'; empty.style.display=''; }
    else if(empty) empty.style.display='none';
  });
}
// highlight a SET of pins (type chip hover): light all their [data-num] + rows
function hiSet(nums){
  clearHi();
  nums.forEach(function(n){ els(n).forEach(function(e){ e.classList.add('hi'); }); });
  paneLit(true);
  // no single readout for a set; leave readouts as-is
}

/* ---------- 3D pane ---------- */
function build3dPane(ctl, body){
  var stg = document.createElement('div'); stg.className='v3d'; body.appendChild(stg);
  var ph = document.createElement('div'); ph.className='ph'; ph.textContent='loading 3D…'; body.appendChild(ph);
  if (!GLB || typeof window.Adom3DViewer === 'undefined') { ph.textContent='3D unavailable'; return; }
  var B = (window.Adom3DViewer && Adom3DViewer.BABYLON) || {};
  var SCENE=null, pinMeshes=new Map(), pinCenter=new Map(), lit3d=[];
  function isHelper(n){ return /arcrotate|background|skybox|indicator|helper|ground|viewcube|viewerroot|laseretch|dbg/i.test(n||''); }
  function realMeshes(s){ return (s.meshes||[]).filter(function(m){ return m.getTotalVertices&&m.getTotalVertices()>0 && !isHelper(m.name); }); }
  function byRe(s,re){ return realMeshes(s).filter(function(m){ return re.test(m.name||''); }); }
  var RE={solder:/solder/i, copper:/copper/i, fr4:/fr4/i};
  var v=null;
  try {
    v=Adom3DViewer.init(stg,{modelUrl:GLB,zUp:true,showGround:false,showViewCube:true,initialViewMode:"perspective"});
    var prev=-1, stable=0, tries=0, iv=setInterval(function(){
      tries++;
      try { var s=v.getScene(), n=realMeshes(s).length; stable=(n>0&&n===prev)?stable+1:0; prev=n;
        if(n>0&&stable>=3){ clearInterval(iv); try{v.frameModel&&v.frameModel();}catch(_){} try{v.goHome&&v.goHome();}catch(_){} ph.remove(); onReady(s); } }catch(_){}
      if(tries>100){ clearInterval(iv); ph.textContent='3D: model did not load'; }
    },200);
  } catch(err){ ph.textContent='3D unavailable: '+err; }

  // lead/pad naming is convention-tolerant: adom board GLBs use pad_N / lead_N /
  // pin_N; chip GLBs from the fit pipeline use U1pN (pad) / U1lN (lead). group1 = pin number.
  var leadRe = /(?:lead|pin|pad|u\d*[pl])[^0-9]?0*(\d+)(?:[^0-9]|$)/i;
  function onReady(s){
    SCENE=s;
    try{ s.lights&&s.lights.forEach(function(l){ l.shadowEnabled=false; }); (s.meshes||[]).forEach(function(m){ m.receiveShadows=false; }); }catch(_){}
    // layer toggles
    [['solder','Solder','#c9ccd1'],['copper','Copper','#d8a24a'],['fr4','Board','#2f7d4f']].forEach(function(t){
      var b=document.createElement('button'); b.className='tog on'; b.setAttribute('data-mesh',t[0]);
      b.innerHTML='<span class="d" style="background:'+t[2]+'"></span>'+t[1];
      b.addEventListener('click', function(){ var on=b.classList.toggle('on'); byRe(s,RE[t[0]]).forEach(function(m){ m.setEnabled(on); }); });
      ctl.appendChild(b);
    });
    // index every lead/pad mesh by pin number + its world centre (for the pick fallback)
    realMeshes(s).forEach(function(m){
      var mm=leadRe.exec(m.name||''); if(!mm) return;
      var n=mm[1]; if(!pinMeshes.has(n))pinMeshes.set(n,[]); pinMeshes.get(n).push(m);
      try{ m.computeWorldMatrix(true); }catch(_){}
    });
    pinMeshes.forEach(function(ms,n){ try{ pinCenter.set(n, ms[0].getBoundingInfo().boundingSphere.centerWorld.clone()); }catch(_){} });
    // 3D -> others: prefer a direct lead hit; else map the hit point to the NEAREST pin
    // (forgiving — the leads are tiny, so a pixel-perfect hit isn't required)
    function pinAtPointer(){
      try{
        var hit=s.pick(s.pointerX,s.pointerY,function(m){ return leadRe.test(m.name||''); });
        if(hit&&hit.hit&&hit.pickedMesh){ var mm=leadRe.exec(hit.pickedMesh.name||''); if(mm) return mm[1]; }
        if(pinCenter.size){
          var any=s.pick(s.pointerX,s.pointerY);
          if(any&&any.hit&&any.pickedPoint){
            var best=null,bd=Infinity;
            pinCenter.forEach(function(c,n){ var d=B.Vector3.DistanceSquared(c,any.pickedPoint); if(d<bd){bd=d;best=n;} });
            return best;
          }
        }
      }catch(_){}
      return null;
    }
    // NOTE: this bundle's Adom3DViewer.BABYLON does NOT expose PointerEventTypes or
    // HighlightLayer — drive off the scene's onPointerMove hook instead.
    var lastPin=null;
    s.onPointerMove=function(){ var n=pinAtPointer(); if(n!==lastPin){ lastPin=n; setPin(n); } };
  }
  // others -> 3D: per-mesh overlay. The GLB shares ONE material across all leads,
  // so emissiveColor would bleed to every pin; renderOverlay is per-mesh and
  // isolates the hovered pin's lead + pad. (HighlightLayer isn't in this bundle.)
  window.__lbr3dHighlight = function(n){
    lit3d.forEach(function(m){ try{ m.renderOverlay=false; }catch(_){} }); lit3d=[];
    if(n==null||!SCENE||!B.Color3) return;
    var ms=pinMeshes.get(String(n)); if(!ms) return;
    ms.forEach(function(m){ try{ m.overlayColor=new B.Color3(0.0,0.90,0.85); m.overlayAlpha=0.55; m.renderOverlay=true; lit3d.push(m); }catch(_){} });
  };
}

/* ---------- boot ---------- */
applyLayout();
CFG.panes.forEach(function(id){
  var slot = mkPane(id);
  if (id==='symbol')    buildSvgPane('symbol','tpl-symbol', false, slot.ctl, slot.body);
  else if (id==='footprint') buildSvgPane('footprint','tpl-footprint', true, slot.ctl, slot.body);
  else if (id==='3d')   build3dPane(slot.ctl, slot.body);
  else if (id==='pins') buildPinsPane(slot.ctl, slot.body);
});
// keep the type-chip sync hooked into apply()
var _apply = apply;
apply = function(n){ _apply(n); if(window.__pinChipSync) window.__pinChipSync(n==null?null:n); };
function reheight(){ try{ parent.postMessage({type:'readme-height',height:Math.max(360,Math.min(16000,document.body.scrollHeight||600))},'*'); }catch(_){} }
window.addEventListener('load', reheight); setTimeout(reheight, 400);
</script>
</body></html>