<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>adom-lbr Manager</title>
<script type="module" src="https://unpkg.com/@google/[email protected]/dist/model-viewer.min.js"></script>
<style>
  :root{--bg:#0f1115;--panel:#171a21;--edge:#252a34;--ink:#e6e9ef;--mut:#8b93a3;--acc:#6ea8fe}
  *{box-sizing:border-box}
  body{margin:0;font:14px/1.5 -apple-system,Segoe UI,Roboto,sans-serif;background:var(--bg);color:var(--ink);height:100vh;display:flex}
  #list{width:300px;min-width:300px;border-right:1px solid var(--edge);background:var(--panel);display:flex;flex-direction:column}
  #list h1{font-size:15px;margin:0;padding:14px 16px;border-bottom:1px solid var(--edge);letter-spacing:.02em}
  #list h1 small{color:var(--mut);font-weight:400}
  #refresh{float:right;background:#0d0f14;border:1px solid var(--edge);color:var(--ink);border-radius:6px;width:26px;height:24px;font-size:14px;cursor:pointer;line-height:1}
  #refresh:hover{background:#243049;border-color:var(--acc)}
  #crumb{padding:7px 16px 0;font-size:12px;color:var(--mut)}
  #crumb a{color:var(--acc);text-decoration:none}
  #crumb a:hover{text-decoration:underline}
  #crumb .cactive{color:var(--ink);font-weight:600}
  .row .tag{display:inline-block;font-size:10px;background:#243049;color:var(--acc);border-radius:4px;padding:1px 5px;margin-left:6px;vertical-align:1px}
  .cathead{padding:7px 16px 5px;font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--acc);background:#12151c;border-bottom:1px solid var(--edge);font-weight:700;position:sticky;top:0;z-index:1}
  .pubsearch{margin-top:6px;border-top:1px solid var(--edge)}
  .pubsearch .pubrow{display:flex;gap:8px;margin:8px 12px}
  .pubsearch input{flex:1;min-width:0;background:#0d0f14;border:1px solid var(--edge);color:var(--ink);border-radius:8px;padding:9px 10px;font-size:13px}
  .pubsearch input:focus{outline:none;border-color:var(--acc)}
  .pubsearch button{background:var(--acc);color:#08111f;border:none;border-radius:8px;padding:0 13px;font-size:19px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center}
  .pubsearch button:hover{filter:brightness(1.1)}
  .row .selbox{margin-right:9px;vertical-align:middle;cursor:pointer}
  #toolbar{border-bottom:1px solid var(--edge);padding:8px 12px;display:flex;flex-direction:column;gap:7px}
  #toolbar .selall{font-size:12px;color:var(--mut);cursor:pointer;user-select:none;display:flex;align-items:center;gap:6px}
  #toolbar .tbtns{display:flex;gap:7px}
  #toolbar button{flex:1;background:#1a2130;border:1px solid var(--edge);color:var(--ink);border-radius:7px;padding:7px 8px;font-size:12px;cursor:pointer}
  #toolbar button:hover{border-color:var(--acc);background:#22304a}
  #toolbar button.primary{background:var(--acc);color:#08111f;border-color:var(--acc);font-weight:600}
  #toolbar button:disabled{opacity:.45;cursor:not-allowed}
  .modal-overlay{position:fixed;inset:0;background:#000a;display:flex;align-items:center;justify-content:center;z-index:1000}
  .modal{background:var(--panel);border:1px solid var(--edge);border-radius:14px;width:min(760px,92vw);max-height:88vh;overflow:auto;padding:22px 24px;box-shadow:0 20px 60px #000a}
  .modal h2{margin:0 0 4px;font-size:18px}
  .modal .msub{color:var(--mut);font-size:13px;margin-bottom:16px}
  .modal .opts{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:16px}
  .modal .opt{border:1px solid var(--edge);border-radius:9px;padding:9px 13px;cursor:pointer;font-size:13px;display:flex;gap:8px;align-items:center}
  .modal .opt.sel{border-color:var(--acc);background:#1a2740}
  .modal table{width:100%;margin:0 0 6px}
  .modal .sure{margin:18px 0 10px;font-size:15px;font-weight:600}
  .modal-actions{display:flex;gap:10px;justify-content:flex-end}
  .modal-actions button{background:#1a2130;border:1px solid var(--edge);color:var(--ink);border-radius:8px;padding:9px 18px;font-size:14px;cursor:pointer}
  .modal-actions button.primary{background:var(--acc);color:#08111f;border-color:var(--acc);font-weight:700}
  .modal .lib{font:12px ui-monospace,monospace;color:#7ee0c0}
  .modal .note{background:#12151c;border:1px solid var(--edge);border-radius:8px;padding:10px 12px;font-size:12px;color:var(--mut);margin:6px 0 4px}
  #modal-dest{margin:0 0 16px}
  #modal-dest label{display:block;font-size:11px;color:var(--mut);margin-bottom:6px;text-transform:uppercase;letter-spacing:.05em}
  #modal-dest .destrow{display:flex;gap:8px}
  #modal-dest select,#modal-dest input{background:#0d0f14;border:1px solid var(--edge);color:var(--ink);border-radius:8px;padding:8px 10px}
  #modal-dest select{min-width:200px}
  #modal-dest input{flex:1;font:12px ui-monospace,monospace}
  #modal-dest input:focus,#modal-dest select:focus{outline:none;border-color:var(--acc)}
  #refresh.spin{animation:sp .6s linear infinite}
  @keyframes sp{to{transform:rotate(360deg)}}
  #q{margin:10px;padding:8px 10px;background:#0d0f14;border:1px solid var(--edge);border-radius:8px;color:var(--ink)}
  #rows{overflow:auto;flex:1}
  .row{padding:10px 16px;border-bottom:1px solid var(--edge);cursor:pointer}
  .row:hover{background:#1e222b}
  .row.sel{background:#243049;border-left:3px solid var(--acc);padding-left:13px}
  .row .mpn{font-weight:600}
  .row .sub{color:var(--mut);font-size:12px}
  #view{flex:1;overflow:auto;padding:18px}
  #hint{color:var(--mut);text-align:center;margin-top:20vh}
  .cols{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px}
  .card{background:var(--panel);border:1px solid var(--edge);border-radius:12px;overflow:hidden;display:flex;flex-direction:column}
  .card h3{margin:0;padding:9px 12px;font-size:12px;text-transform:uppercase;letter-spacing:.06em;color:var(--mut);border-bottom:1px solid var(--edge)}
  .card .body{aspect-ratio:1;width:100%;display:flex;align-items:center;justify-content:center;padding:10px;background:#161b22;overflow:hidden;cursor:grab;touch-action:none;position:relative}
  .card .body svg{max-width:100%;max-height:100%;will-change:transform}
  .card .hint2{position:absolute;bottom:6px;right:8px;font-size:10px;color:#b9c0cc;background:#0d0f14aa;border-radius:5px;padding:1px 6px;pointer-events:none}
  .lfilter{display:flex;flex-wrap:wrap;gap:6px;padding:8px 10px;border-bottom:1px solid var(--edge)}
  .chip{display:inline-flex;align-items:center;gap:5px;font-size:11px;color:var(--ink);background:#0d0f14;border:1px solid var(--edge);border-radius:6px;padding:3px 7px;cursor:pointer;user-select:none}
  .chip input{margin:0;cursor:pointer}
  .chip .sw{width:9px;height:9px;border-radius:2px;display:inline-block}
  .chip:has(input:not(:checked)){opacity:.4;text-decoration:line-through}
  model-viewer{width:100%;aspect-ratio:1;height:auto;background:#161b22}
  h2.title{margin:0 0 14px}
  h2.title small{color:var(--mut);font-weight:400;font-size:14px}
  table{width:100%;border-collapse:collapse;margin-top:16px;background:var(--panel);border:1px solid var(--edge);border-radius:12px;overflow:hidden}
  th,td{text-align:left;padding:10px 14px;border-bottom:1px solid var(--edge);font-size:13px}
  th{color:var(--mut);font-weight:600;text-transform:uppercase;font-size:11px;letter-spacing:.05em}
  tr:last-child td{border-bottom:none}
  .swatch{display:inline-block;width:11px;height:11px;border-radius:3px;margin-right:7px;vertical-align:-1px}
  .badge{font:12px ui-monospace,monospace;background:#0d0f14;border:1px solid var(--edge);border-radius:6px;padding:2px 7px}
  .stacktitle{margin:22px 0 6px;font-size:13px;text-transform:uppercase;letter-spacing:.05em;color:var(--mut)}
  .err{color:#f87171;padding:16px}
  .card .body [data-num]{cursor:pointer}
  .pinhi .pinvis{stroke:#6ea8fe !important;vector-effect:non-scaling-stroke;stroke-width:3}
  rect.pad.pinhi{stroke:#6ea8fe;vector-effect:non-scaling-stroke;stroke-width:3}
  #tip{position:fixed;z-index:9999;background:#0d1117;border:1px solid var(--acc);color:var(--ink);font:12px ui-monospace,monospace;padding:3px 8px;border-radius:6px;pointer-events:none;display:none;white-space:nowrap}
  #tip b{color:var(--acc)}
</style>
</head>
<body>
  <aside id="list">
    <h1>adom-lbr <small>Manager</small><button id="refresh" title="refresh list (fetch newly-pushed boards)">↻</button></h1>
    <div id="crumb"></div>
    <input id="q" placeholder="filter boards…">
    <div id="toolbar" style="display:none">
      <label class="selall"><input type="checkbox" id="selall"> <span id="selcount">select all</span></label>
      <div class="tbtns">
        <button id="btn-addlib" disabled>Add to Library</button>
        <button id="btn-addproj">Add Project ↓</button>
      </div>
    </div>
    <div id="rows"></div>
  </aside>
  <main id="view"><div id="hint">← pick a board, then a component, to open its viewer</div></main>
  <div id="modal" class="modal-overlay" style="display:none"><div class="modal">
    <h2 id="modal-title"></h2><div class="msub" id="modal-sub"></div>
    <div class="opts" id="modal-opts"></div>
    <div id="modal-dest"></div>
    <div id="modal-preview"></div>
    <div class="sure">Are you sure this is good?</div>
    <div class="modal-actions"><button id="modal-cancel">Cancel</button><button class="primary" id="modal-go">Yes, add it</button></div>
  </div></div>

<script>
const COLORS={Silkscreen:'#d8b4fe',Courtyard:'#4ade80','Fabrication / Assembly':'#f472b6',Copper:'#f59e0b',Other:'#94a3b8'};
// Board-centric navigation: Boards (tagged board/molecule) ▸ a board's component
// dependencies ▸ the symbol/footprint/3D viewer.
let all=[], bySlug={}, boardSlug=null, curDeps=[], me='', pubQuery='';
const isBoard=c=>{const t=(c.tags||[]).map(x=>(''+x).toLowerCase());return t.includes('board')||t.includes('molecule');};
const qval=()=>document.getElementById('q').value.toLowerCase();
const rowsEl=()=>document.getElementById('rows');
function makeRow(slug,title,sub,onclick,tag,sel){
  const d=document.createElement('div');d.className='row';d.dataset.slug=slug;
  const cb=sel?`<input type="checkbox" class="selbox"${selected.has(slug)?' checked':''}>`:'';
  d.innerHTML=`<div class="mpn">${cb}${title}${tag?`<span class="tag">${tag}</span>`:''}</div><div class="sub">${sub}</div>`;
  d.onclick=(e)=>{ if(e.target.classList.contains('selbox')){toggleSel(slug,e.target.checked);e.stopPropagation();return;} onclick(slug,d); };
  return d;
}
// component-type → the user's existing KiCad symbol library (fallback JLC_Misc_BLT)
const TYPE_TO_SYMLIB={'Capacitors':'JLC_Capacitors_BLT','Resistors':'JLC_Resistors_BLT','MOSFETs':'JLC_FETs_BLT','Transistors':'JLC_FETs_BLT','Inductors':'JLC_Inductors_BLT','LEDs':'JLC_LEDs_BLT','Diodes':'JLC_Diodes_BLT','Connectors':'JLC_Connectors_BLT','Microcontrollers':'JLC_MCUs_BLT','Power ICs':'JLC_Power_BLT','Crystals & Oscillators':'JLC_Oscillators_BLT','ICs':'JLC_Misc_BLT','Other':'JLC_Misc_BLT'};
const selected=new Set();
function symLibFor(c){return TYPE_TO_SYMLIB[typeOf(c)]||'JLC_Misc_BLT';}
function toggleSel(slug,on){on?selected.add(slug):selected.delete(slug);updateToolbar();}
function updateToolbar(){
  const n=selected.size;
  const cnt=document.getElementById('selcount'); if(cnt)cnt.textContent=n?`${n} selected · clear`:'select all';
  const b=document.getElementById('btn-addlib'); if(b){b.disabled=n===0;b.textContent=n?`Add ${n} to Library`:'Add to Library';}
  const sa=document.getElementById('selall'); if(sa)sa.checked=curDeps.length>0&&curDeps.every(s=>selected.has(s));
}
async function loadList(fresh){
  const btn=document.getElementById('refresh'); if(btn&&fresh)btn.classList.add('spin');
  if(!fresh) rowsEl().innerHTML='<div class="row" style="color:#8b93a3;cursor:default">loading boards from wiki…</div>';
  try{
    const r=await fetch('api/components'+(fresh?'?refresh=1':'')).then(r=>r.json());
    all=r.components||[]; me=r.me||''; bySlug={}; all.forEach(c=>bySlug[c.slug]=c);
    render();
  }catch(e){
    rowsEl().innerHTML='<div class="row" style="color:#f87171;cursor:default">failed to load — retry</div>';
  }finally{ if(btn)btn.classList.remove('spin'); }
}
function render(){ boardSlug?renderDeps():renderBoards(); }
function renderBoards(){
  document.getElementById('toolbar').style.display='none';selected.clear();
  document.getElementById('crumb').innerHTML='<span class="cactive">Projects</span>';
  document.getElementById('q').placeholder='filter projects…';
  const q=qval(),el=rowsEl();el.innerHTML='';
  const boards=all.filter(isBoard);
  const mineAll=boards.filter(c=>c.owner&&c.owner===me);
  const pubAll=boards.filter(c=>!(c.owner&&c.owner===me));
  // My projects — a normal list (filtered by the top box)
  const mine=mineAll.filter(c=>!q||c.slug.includes(q)||(c.title||'').toLowerCase().includes(q));
  const h=document.createElement('div');h.className='cathead';h.textContent=`My projects · ${mine.length}`;el.appendChild(h);
  if(!mine.length){const d=document.createElement('div');d.className='row';d.style.cssText='color:#8b93a3;cursor:default;line-height:1.5';d.innerHTML='none yet — push a board tagged <b>board</b>/<b>molecule</b> to your wiki, then press ↻';el.appendChild(d);}
  mine.forEach(c=>el.appendChild(makeRow(c.slug,c.title||c.slug,c.slug,openBoard,'board')));
  // Public projects — type a query, press Enter or the button to load matches
  const wrap=document.createElement('div');wrap.className='pubsearch';
  wrap.innerHTML=`<div class="cathead">Public projects</div><div class="pubrow"><input id="pubq" placeholder="search public projects"><button id="pubgo" title="search">⏎</button></div><div id="pubresults"></div>`;
  el.appendChild(wrap);
  const pubq=document.getElementById('pubq');pubq.value=pubQuery;
  const renderPub=()=>{
    const pr=document.getElementById('pubresults');pr.innerHTML='';const pq=pubQuery.trim().toLowerCase();
    if(!pq)return;
    const m=pubAll.filter(c=>c.slug.includes(pq)||(c.title||'').toLowerCase().includes(pq)||(c.brief||'').toLowerCase().includes(pq));
    if(!m.length){pr.innerHTML=`<div class="row" style="color:#8b93a3;cursor:default">no public projects match “${pubQuery.trim()}”</div>`;return;}
    m.forEach(c=>pr.appendChild(makeRow(c.slug,c.title||c.slug,c.slug,openBoard,'board')));
  };
  const doPub=()=>{pubQuery=pubq.value;renderPub();};
  pubq.onkeydown=e=>{if(e.key==='Enter')doPub();};
  document.getElementById('pubgo').onclick=doPub;
  renderPub();
}
async function openBoard(slug){
  boardSlug=slug; curDeps=[]; selected.clear();
  const title=(bySlug[slug]||{}).title||slug;
  document.getElementById('crumb').innerHTML=`<a href="#" onclick="backToBoards();return false">← Boards</a> / <span class="cactive">${title}</span>`;
  rowsEl().innerHTML='<div class="row" style="color:#8b93a3;cursor:default">loading dependencies…</div>';
  try{ const r=await fetch('api/board/'+slug).then(r=>r.json()); curDeps=(r.deps||[]).map(d=>d.slug); }
  catch(e){ curDeps=[]; }
  renderDeps();
}
// Infer a component's type from its tags + brief + title (most parts carry no
// explicit category, so keywords in the brief are the signal).
const TYPE_RULES=[
  [/microcontroller|\bmcu\b|\bmpu\b|\bsoc\b/,'Microcontrollers'],
  [/regulator|\bldo\b|dc-?dc|buck|boost|power management|\bpmic\b/,'Power ICs'],
  [/mosfet|\bfet\b/,'MOSFETs'],
  [/transistor|\bbjt\b|\bnpn\b|\bpnp\b/,'Transistors'],
  [/\bled\b/,'LEDs'],
  [/diode|schottky|zener|\btvs\b|rectifier/,'Diodes'],
  [/mlcc|capacitor|\bcap\b|tantalum/,'Capacitors'],
  [/resistor|thick film|thin film/,'Resistors'],
  [/inductor|chip coil|\bcoil\b|ferrite|choke|\bbead\b/,'Inductors'],
  [/crystal|oscillator|resonator/,'Crystals & Oscillators'],
  [/connector|header|receptacle|socket|\bjack\b|\busb\b/,'Connectors'],
  [/switch|button|tactile/,'Switches'],
  [/\bfuse\b/,'Fuses'],
  [/amplifier|op-?amp|\bdriver\b|sensor|\badc\b|\bdac\b|transceiver|controller|\bgate\b|logic|\bic\b/,'ICs'],
];
const TYPE_ORDER=['Microcontrollers','ICs','Power ICs','MOSFETs','Transistors','Diodes','LEDs','Resistors','Capacitors','Inductors','Crystals & Oscillators','Connectors','Switches','Fuses','Other'];
function typeOf(c){
  const hay=(((c.tags||[]).join(' '))+' '+(c.brief||'')+' '+(c.title||'')+' '+(c.slug||'')).toLowerCase();
  for(const [re,label] of TYPE_RULES) if(re.test(hay)) return label;
  return 'Other';
}
function renderDeps(){
  document.getElementById('q').placeholder='filter components…';
  document.getElementById('toolbar').style.display='flex';
  const q=qval(),el=rowsEl();el.innerHTML='';
  if(!curDeps.length){el.innerHTML='<div class="row" style="color:#8b93a3;cursor:default">this board lists no component dependencies</div>';updateToolbar();return;}
  const items=curDeps.map(s=>({slug:s,c:bySlug[s]||{slug:s}}))
    .filter(x=>!q||x.slug.includes(q)||((x.c.title||'').toLowerCase().includes(q))||((x.c.brief||'').toLowerCase().includes(q)));
  const groups={};
  items.forEach(x=>{const t=typeOf(x.c);(groups[t]=groups[t]||[]).push(x);});
  const cats=Object.keys(groups).sort((a,b)=>{const ia=TYPE_ORDER.indexOf(a),ib=TYPE_ORDER.indexOf(b);return (ia<0?99:ia)-(ib<0?99:ib)||a.localeCompare(b);});
  cats.forEach(cat=>{
    const h=document.createElement('div');h.className='cathead';h.textContent=`${cat} · ${groups[cat].length}`;el.appendChild(h);
    groups[cat].forEach(x=>{const on=bySlug[x.slug];el.appendChild(makeRow(x.slug,x.c.title||x.slug,on?x.slug:x.slug+' · not on wiki',openPart,null,true));});
  });
  updateToolbar();
}
function backToBoards(){boardSlug=null;curDeps=[];document.getElementById('q').value='';document.querySelector('#view #hint')||(document.getElementById('view').innerHTML='<div id="hint">← pick a board, then a component, to open its viewer</div>');render();}
async function openPart(slug,el){
  document.querySelectorAll('.row').forEach(r=>r.classList.remove('sel'));
  if(el)el.classList.add('sel');
  const v=document.getElementById('view');
  v.innerHTML='<div id="hint">loading '+slug+'…</div>';
  const p=await fetch('api/part/'+slug).then(r=>r.json());
  if(p.status!=='ok'){v.innerHTML='<div class="err">'+(p.error||'failed')+'</div>';return;}
  const glb=p.glb?`<model-viewer src="glb/${slug}" camera-controls auto-rotate ar shadow-intensity="1"></model-viewer>`:'<div class="body" style="color:#888;background:#0d0f14">no 3D model</div>';
  let rows=(p.layers||[]).map(l=>`<tr>
      <td><span class="swatch" style="background:${COLORS[l.role]||'#888'}"></span>${l.role}<span style="color:#8b93a3"> ×${l.count}</span></td>
      <td><span class="badge">${l.kicad}</span></td>
      <td><span class="badge">${l.altium}</span></td></tr>`).join('');
  v.innerHTML=`
    <h2 class="title">${p.mpn} <small>· ${p.pins} pins · ${p.pads} pads</small></h2>
    <div class="cols">
      <div class="card"><h3>Schematic Symbol</h3><div class="body">${p.symbol_svg}<span class="hint2">drag · scroll to zoom</span></div></div>
      <div class="card"><h3>Footprint</h3>
        <div class="lfilter">${(p.layers||[]).map(l=>`<label class="chip"><input type="checkbox" checked data-key="${l.key}"><span class="sw" style="background:${COLORS[l.role]||'#888'}"></span>${l.role}</label>`).join('')}</div>
        <div class="body" id="fpbody">${p.footprint_svg}<span class="hint2">drag · scroll to zoom</span></div></div>
      <div class="card"><h3>3D Model</h3>${glb}</div>
    </div>
    <div class="stacktitle">Footprint layer stackup — what each element becomes per EDA tool</div>
    <table><thead><tr><th>Element (neutral role)</th><th>KiCad layer</th><th>Altium layer</th></tr></thead>
    <tbody>${rows}</tbody></table>`;
  v.querySelectorAll('.card .body').forEach(makePannable);
  // footprint layer filter — toggle visibility of each layer's SVG elements
  v.querySelectorAll('.lfilter input').forEach(cb=>{
    cb.onchange=()=>{
      v.querySelectorAll('#fpbody svg .ly-'+cb.dataset.key).forEach(el=>{el.style.display=cb.checked?'':'none';});
    };
  });
  setupHighlight(v);
}

// Hover a pin (symbol) or pad (footprint) → highlight it + show its number and
// name/description in a tooltip. Pins/pads carry data-num + data-name.
function setupHighlight(v){
  let tip=document.getElementById('tip');
  if(!tip){tip=document.createElement('div');tip.id='tip';document.body.appendChild(tip);}
  const clear=box=>box.querySelectorAll('.pinhi').forEach(x=>x.classList.remove('pinhi'));
  v.querySelectorAll('.card .body').forEach(box=>{
    box.addEventListener('pointermove',e=>{
      const hit=e.target.closest('[data-num]');
      clear(box);
      if(hit){
        hit.classList.add('pinhi');
        const num=hit.dataset.num, nm=hit.dataset.name;
        const kind=hit.closest('#fpbody')?'Pad':'Pin';
        tip.innerHTML=(nm&&nm!=='~')?`<b>${kind} ${num}</b> · ${nm}`:`<b>${kind} ${num}</b>`;
        tip.style.display='block';
        tip.style.left=(e.clientX+14)+'px';
        tip.style.top=(e.clientY+14)+'px';
      } else { tip.style.display='none'; }
    });
    box.addEventListener('pointerleave',()=>{clear(box);tip.style.display='none';});
  });
}

// Drag-to-pan + scroll-to-zoom on an SVG viewer box (pointer capture so a drag
// that leaves the box keeps tracking). Re-assigning on* handlers avoids stacking.
function makePannable(box){
  const svg=box.querySelector('svg');
  if(!svg) return;
  let tx=0,ty=0,scale=1,sx=0,sy=0,drag=false;
  const apply=()=>{svg.style.transform=`translate(${tx}px,${ty}px) scale(${scale})`;};
  box.onpointerdown=e=>{drag=true;sx=e.clientX-tx;sy=e.clientY-ty;box.setPointerCapture(e.pointerId);box.style.cursor='grabbing';};
  box.onpointermove=e=>{if(drag){tx=e.clientX-sx;ty=e.clientY-sy;apply();}};
  box.onpointerup=box.onpointercancel=()=>{drag=false;box.style.cursor='grab';};
  box.onwheel=e=>{e.preventDefault();const f=e.deltaY<0?1.12:0.89;scale=Math.max(0.25,Math.min(12,scale*f));apply();};
  box.ondblclick=()=>{tx=ty=0;scale=1;apply();};
}
// ---- selection controls + add-to-library / add-project modal ----
document.getElementById('selall').onchange=(e)=>{const on=e.target.checked;curDeps.forEach(s=>on?selected.add(s):selected.delete(s));renderDeps();};
function previewTable(plan){
  return `<table><thead><tr><th>Component</th><th>Type</th><th>Symbol → library</th><th>Footprint → library</th></tr></thead><tbody>${
    plan.map(p=>`<tr><td>${p.title}</td><td>${p.type}</td><td><span class="lib">${p.symLib}.kicad_sym</span></td><td><span class="lib">${p.fpLib}.pretty</span></td></tr>`).join('')}</tbody></table>`;
}
function buildPlan(slugs,projectLib,projName){
  return slugs.map(s=>{const c=bySlug[s]||{slug:s,title:s};const t=typeOf(c);
    return {slug:s,title:c.title||s,type:t,symLib:projectLib?projName:symLibFor(c),fpLib:projectLib?projName:'adom_lbr'};});
}
let modalGo=null;
const showModal=()=>document.getElementById('modal').style.display='flex';
const hideModal=()=>{document.getElementById('modal').style.display='none';modalGo=null;const b=document.getElementById('applybar');if(b)b.remove();};
document.getElementById('modal-cancel').onclick=hideModal;
document.getElementById('modal-go').onclick=()=>{if(modalGo)modalGo();};
function selOpt(sel,attr,cb){document.querySelectorAll('#modal-opts .opt').forEach(o=>o.onclick=()=>{document.querySelectorAll('#modal-opts .opt').forEach(x=>x.classList.remove('sel'));o.classList.add('sel');o.querySelector('input').checked=true;cb(o.dataset[attr]);});}
// recommended destination parents for delivered projects (editable in the box)
const DEST_FOLDERS=['/home/barrett-land/Documents/KiCAD/Projects','/home/barrett-land/Documents/Adom/Molecules/Other Molecules','/home/barrett-land/Documents/Adom/Electrical','/home/barrett-land/Downloads'];
function openAddLib(){
  const slugs=[...selected]; if(!slugs.length)return;
  const plan=buildPlan(slugs,false);
  document.getElementById('modal-dest').innerHTML='';
  document.getElementById('modal-title').textContent='Add to Library';
  document.getElementById('modal-sub').textContent=`${slugs.length} component(s) → your existing KiCad libraries`;
  document.getElementById('modal-opts').innerHTML=`<div class="opt sel" data-eda="kicad"><input type="radio" name="eda" checked> KiCad</div><div class="opt" data-eda="altium"><input type="radio" name="eda"> Altium (soon)</div>`;
  const go=document.getElementById('modal-go');
  selOpt('eda','eda',v=>{document.getElementById('modal-preview').innerHTML=v==='altium'?'<div class="note">Altium add-to-library isn\'t wired up yet — pick KiCad for now.</div>':previewTable(plan);go.disabled=v==='altium';});
  document.getElementById('modal-preview').innerHTML=previewTable(plan);
  go.disabled=false;go.textContent='Yes, add it';
  modalGo=()=>doApply(plan,'kicad',false);
  showModal();
}
function openAddProj(){
  const slugs=curDeps.slice();
  const projName=((bySlug[boardSlug]||{}).title||boardSlug).replace(/[^A-Za-z0-9_-]+/g,'_');
  let mode='personal';
  document.getElementById('modal-title').textContent='Add Project to my Computer';
  document.getElementById('modal-sub').textContent=`schematic + ${slugs.length} component(s) — symbols & footprints resolved so it opens clean`;
  document.getElementById('modal-opts').innerHTML=`<div class="opt sel" data-mode="personal"><input type="radio" name="mode" checked> Integrate into my libraries</div><div class="opt" data-mode="project"><input type="radio" name="mode"> Project-only library</div>`;
  // editable destination + recommended-folder presets
  document.getElementById('modal-dest').innerHTML=`<label>Deliver project to</label><div class="destrow"><select id="destsel">${DEST_FOLDERS.map(f=>`<option value="${f}">${f.replace('/home/barrett-land','~')}</option>`).join('')}</select><input id="destpath" spellcheck="false"></div>`;
  const destpath=document.getElementById('destpath'), destsel=document.getElementById('destsel');
  destpath.value=DEST_FOLDERS[0]+'/'+boardSlug;
  destsel.onchange=()=>{destpath.value=destsel.value+'/'+boardSlug;};
  const draw=()=>{
    const plan=buildPlan(slugs,mode==='project',projName);
    document.getElementById('modal-preview').innerHTML=`<div class="note">${mode==='project'?'A self-contained <b>'+projName+'</b> symbol + footprint library is created for this project — your personal libraries stay untouched.':'Symbols merge into your existing per-type libraries; footprints go to <b>adom_lbr.pretty</b>. The schematic library references get registered so nothing shows as broken.'}</div>`+previewTable(plan);
    modalGo=()=>doApply(plan,'kicad',mode==='project',boardSlug,document.getElementById('destpath').value);
  };
  selOpt('mode','mode',v=>{mode=v;draw();});
  document.getElementById('modal-go').disabled=false;document.getElementById('modal-go').textContent='Yes, add the project';
  draw();showModal();
}
async function doApply(plan,eda,project,board,dest){
  const go=document.getElementById('modal-go');go.disabled=true;go.textContent='Starting…';
  const opts=document.getElementById('modal-opts');
  const start=await fetch('api/apply',{method:'POST',headers:{'Content-Type':'application/json'},
    body:JSON.stringify({target:eda,project:!!project,board:board||null,dest:dest||'',items:plan.map(p=>({slug:p.slug,symLib:p.symLib,fpLib:p.fpLib}))})}).then(r=>r.json()).catch(()=>({status:'error',error:'request failed'}));
  if(start.status!=='started'){alert(start.error||'failed');go.textContent='Yes, add it';go.disabled=false;return;}
  // live status bar above the preview
  let bar=document.getElementById('applybar');
  if(!bar){bar=document.createElement('div');bar.id='applybar';bar.className='note';opts.parentNode.insertBefore(bar,opts.nextSibling);}
  const poll=setInterval(async()=>{
    let p; try{p=await fetch('api/apply-progress').then(r=>r.json());}catch(e){return;}
    const pct=p.total?` · ${p.current}/${p.total}`:'';
    go.textContent=(p.done?'✓ ':'')+(p.stage||'Working…').replace(/…$/,'').slice(0,22)+'…';
    bar.innerHTML=`<b>${p.stage||'Working…'}</b>${pct}`;
    if(p.done){
      clearInterval(poll);
      const r=p.result||{}, pf=(r.project&&r.project.files||[]);
      go.textContent=`✓ Added ${r.count||0}`;
      const folder=(r.project&&r.project.folder||'').replace('/home/barrett-land','~');
      bar.innerHTML=`<b>✓ Done.</b> Merged ${r.count||0} symbol(s) into your libraries.`+
        (pf.length?`<br>Delivered ${pf.length} project file(s) → <span class="lib">${folder}</span>`:'')+
        ((r.errors&&r.errors.length)?`<br><span style="color:#f87171">${r.errors.length} issue(s): ${r.errors.slice(0,2).join('; ')}</span>`:'');
      setTimeout(hideModal,pf.length||(r.errors&&r.errors.length)?3600:1600);
    }
  },350);
}
document.getElementById('btn-addlib').onclick=openAddLib;
document.getElementById('btn-addproj').onclick=openAddProj;

document.getElementById('q').oninput=render;
document.getElementById('refresh').onclick=()=>loadList(true);
loadList();
</script>
</body>
</html>