<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>adom-lbr</title>
  <link rel="icon" type="image/svg+xml" href="favicon.svg">
  <style>
    :root {
      --bg:#0d1117; --panel:#11161f; --panel2:#0f141c; --border:#222b38; --border2:#2b3645;
      --text:#e6edf3; --muted:#8b949e; --text3:#5b6675; --accent:#00b8b1; --accent2:#00e6dc;
      --ok:#2ea043; --ok2:#3fb950; --err:#f85149; --warn:#d29922;
    }
    * { box-sizing:border-box; }
    html,body { margin:0; height:100%; }
    body { background:var(--bg); color:var(--text); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; display:flex; flex-direction:column; }

    header.app {
      height:44px; flex:0 0 44px; display:grid; grid-template-columns:auto 1fr auto;
      align-items:center; gap:14px; padding:0 14px; border-bottom:1px solid var(--border);
      background:linear-gradient(180deg,#11161f,#0d1117);
    }
    header.app .ident { display:flex; align-items:center; gap:8px; min-width:0; }
    header.app .ident svg { width:16px; height:16px; color:var(--accent2); flex:0 0 16px; }
    header.app .app-name { font-weight:700; font-size:13px; letter-spacing:.2px; white-space:nowrap; }
    header.app .app-sub { color:var(--muted); font-size:12px; white-space:nowrap; }
    header.app .app-sub::before { content:"·"; margin:0 6px; color:var(--text3); }
    header.app .subject { justify-self:center; display:flex; align-items:baseline; gap:10px; min-width:0; }
    header.app .subject-name { font-weight:600; font-size:13px; max-width:42vw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    header.app .subject-meta { font-family:'JetBrains Mono',ui-monospace,monospace; font-size:11px; color:var(--muted); white-space:nowrap; }
    header.app .actions { display:flex; align-items:center; gap:8px; }
    header.app .conn { display:inline-flex; align-items:center; gap:6px; font-size:11px; color:var(--muted); }
    header.app .conn .dot { width:8px; height:8px; border-radius:50%; background:var(--text3); transition:background .2s, box-shadow .2s; }
    header.app .conn.live .dot { background:var(--accent2); box-shadow:0 0 8px var(--accent2); }
    header.app .sendto { display:inline-flex; align-items:center; gap:4px; }
    header.app .sendto .sendlabel { font-size:11px; color:var(--muted); margin-right:2px; }
    header.app .sendto .sbtn { font:inherit; font-size:11px; color:var(--text2,#cfd6e4); background:var(--surface2,#1c2230); border:1px solid var(--border,#2a3140); border-radius:6px; padding:3px 8px; cursor:pointer; transition:background .15s,border-color .15s,color .15s; }
    header.app .sendto .sbtn:hover { background:var(--accent2,#37c8a6); border-color:var(--accent2,#37c8a6); color:#0b0f17; }
    header.app .sendto .sbtn:disabled { opacity:.5; cursor:progress; }
    #toast { position:fixed; left:50%; bottom:18px; transform:translateX(-50%) translateY(20px); background:#1c2230; color:#e8ecf4; border:1px solid #2a3140; border-radius:10px; padding:10px 16px; font-size:13px; max-width:84vw; box-shadow:0 8px 28px rgba(0,0,0,.45); opacity:0; pointer-events:none; transition:opacity .25s, transform .25s; z-index:9999; }
    #toast.show { opacity:1; transform:translateX(-50%) translateY(0); }
    #toast.ok { border-color:#37c8a6; } #toast.err { border-color:#e0698a; }
    #bridgeModal{position:fixed;inset:0;z-index:100001;display:none;align-items:center;justify-content:center;background:rgba(2,5,10,.62);backdrop-filter:blur(4px);padding:20px}
    #bridgeModal.show{display:flex}
    #bridgeModal .bm-card{max-width:min(94vw,440px);background:rgba(17,22,31,.99);border:1px solid var(--border2,#2b3645);border-radius:16px;padding:22px 22px 18px;box-shadow:0 30px 90px rgba(0,0,0,.7);text-align:center}
    #bridgeModal .bm-icon{font-size:34px;margin-bottom:6px}
    #bridgeModal .bm-title{font-size:16px;font-weight:700;margin-bottom:8px;color:var(--text,#e6edf3)}
    #bridgeModal .bm-body{font-size:13px;line-height:1.55;color:var(--muted,#8b949e);margin-bottom:16px}
    #bridgeModal .bm-actions{display:flex;flex-direction:column;gap:9px}
    #bridgeModal .bm-go{display:block;text-decoration:none;background:linear-gradient(180deg,#00b8b1,#009a94);color:#04201f;font-weight:700;font-size:13px;padding:10px;border-radius:10px}
    #bridgeModal .bm-dismiss{background:transparent;border:1px solid var(--border2,#2b3645);color:var(--muted,#8b949e);font-size:12.5px;padding:8px;border-radius:10px;cursor:pointer}
    #bridgeModal .bm-dismiss:hover{color:var(--text,#e6edf3)}

    main { flex:1; display:flex; align-items:flex-start; justify-content:center; padding:28px; min-height:0; overflow:auto; }
    .card { background:var(--panel); border:1px solid var(--border); border-radius:14px; padding:22px 24px; box-shadow:0 24px 80px rgba(0,0,0,.5); max-width:min(94vw,720px); width:100%; }
    .topline { display:flex; align-items:center; justify-content:space-between; gap:14px; }
    .libname { font-size:21px; font-weight:700; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    .badge { flex:0 0 auto; display:inline-flex; align-items:center; gap:7px; font-size:13px; font-weight:700; padding:6px 13px; border-radius:999px; }
    .badge .pip { width:9px; height:9px; border-radius:50%; }
    .badge.pass { background:rgba(46,160,67,.14); color:var(--ok2); border:1px solid rgba(46,160,67,.4); }
    .badge.pass .pip { background:var(--ok2); box-shadow:0 0 8px var(--ok2); }
    .badge.fail { background:rgba(248,81,73,.13); color:var(--err); border:1px solid rgba(248,81,73,.4); }
    .badge.fail .pip { background:var(--err); box-shadow:0 0 8px var(--err); }

    .counts { display:flex; flex-wrap:wrap; gap:10px; margin-top:18px; }
    .stat { flex:1 1 0; min-width:84px; background:var(--panel2); border:1px solid var(--border); border-radius:10px; padding:11px 13px; }
    .stat .n { font-size:20px; font-weight:700; font-family:'JetBrains Mono',ui-monospace,monospace; }
    .stat .k { color:var(--muted); font-size:11px; margin-top:1px; text-transform:uppercase; letter-spacing:.4px; }

    .section { margin-top:18px; }
    .section h3 { font-size:11px; letter-spacing:.5px; text-transform:uppercase; color:var(--muted); margin:0 0 8px; }
    .issue { display:flex; gap:9px; align-items:flex-start; padding:9px 11px; border-radius:9px; font-size:13px; line-height:1.45; margin-bottom:7px; }
    .issue.e { background:rgba(248,81,73,.08); border:1px solid rgba(248,81,73,.28); }
    .issue.w { background:rgba(210,153,34,.08); border:1px solid rgba(210,153,34,.28); }
    .issue .code { flex:0 0 auto; font-family:'JetBrains Mono',ui-monospace,monospace; font-size:11px; padding:1px 7px; border-radius:6px; margin-top:1px; }
    .issue.e .code { background:rgba(248,81,73,.18); color:var(--err); }
    .issue.w .code { background:rgba(210,153,34,.18); color:var(--warn); }
    .clean { color:var(--ok2); font-size:13px; display:flex; align-items:center; gap:8px; }
    .empty { color:var(--text3); font-size:13px; padding:48px 20px; text-align:center; }
    code { font-family:'JetBrains Mono',ui-monospace,monospace; background:var(--panel2); border:1px solid var(--border); border-radius:5px; padding:1px 6px; font-size:.9em; }
    footer { flex:0 0 auto; text-align:center; color:var(--text3); font-size:11px; padding:8px; border-top:1px solid var(--border); }

    #tt { position:fixed; z-index:99999; max-width:340px; background:rgba(22,27,34,0.97); backdrop-filter:blur(16px);
      border:1px solid rgba(0,184,177,0.25); border-radius:10px; padding:10px 14px; color:#e6edf3;
      font-family:-apple-system,sans-serif; font-size:12px; font-weight:400; letter-spacing:0; text-transform:none;
      line-height:1.55; white-space:pre-wrap; pointer-events:none; opacity:0; transition:opacity .18s ease;
      box-shadow:0 12px 40px rgba(0,0,0,0.55); }
    #tt.show { opacity:1; }
    [data-tooltip] { cursor:help; }
  </style>
</head>
<body>
  <header class="app">
    <div class="ident" data-tooltip="adom-lbr — a standalone Rust tool that converts KiCad symbols/footprints into EAGLE .lbr libraries for Fusion 360 Electronics, then lints them so Fusion won't silently show 'No Components'. No local EAGLE/Fusion install needed.">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5.5A1.5 1.5 0 0 1 5.5 4H9a1.5 1.5 0 0 1 1.5 1.5V20a1.5 1.5 0 0 0-1.5-1.5H5.5A1.5 1.5 0 0 1 4 17z"/><path d="M20 5.5A1.5 1.5 0 0 0 18.5 4H15a1.5 1.5 0 0 0-1.5 1.5V20a1.5 1.5 0 0 1 1.5-1.5h3.5a1.5 1.5 0 0 0 1.5-1.5z"/></svg>
      <span class="app-name">adom-lbr</span>
      <span class="app-sub">EAGLE library linter</span>
    </div>
    <div class="subject" id="subject">
      <span class="subject-name" id="subjectName">—</span>
      <span class="subject-meta" id="subjectMeta"></span>
    </div>
    <div class="actions">
      <span class="sendto" id="sendto" style="display:none" data-tooltip="Send this library to a desktop EDA tool via Adom Desktop. Fusion 360 / Altium / OrCAD import the .lbr directly; KiCad receives a native .kicad_sym converted from it.">
        <span class="sendlabel">Send to</span>
        <button class="sbtn" data-target="kicad">KiCad</button>
        <button class="sbtn" data-target="fusion360">Fusion</button>
        <button class="sbtn" data-target="altium">Altium</button>
        <button class="sbtn" data-target="orcad">OrCAD</button>
      </span>
      <span class="conn" id="conn" data-tooltip="Live connection to the adom-lbr Rust server. Green = the UI is polling /state and the AI can drive this view in real time (push an .lbr via /load, run /eval, read the console).">
        <span class="dot"></span><span id="connText">connecting…</span>
      </span>
    </div>
  </header>

  <main>
    <div class="card" id="card">
      <div class="empty" id="empty">Waiting for a library… run <code>adom-lbr generate</code> then push the <code>.lbr</code>, or the AI will load one here.</div>
      <div id="content" style="display:none">
        <div class="topline">
          <div class="libname" id="libName"></div>
          <div class="badge" id="badge"><span class="pip"></span><span id="badgeText"></span></div>
        </div>
        <div class="counts" id="counts"></div>
        <div class="section" id="errSection" style="display:none">
          <h3 data-tooltip="Hard errors block the component in Fusion 360 — an empty packages/connects section or a malformed deviceset makes the part import as 'No Components'.">Errors</h3>
          <div id="errList"></div>
        </div>
        <div class="section" id="warnSection" style="display:none">
          <h3 data-tooltip="Warnings won't block import but usually mean the symbol/pads won't look or connect the way you expect in Fusion.">Warnings</h3>
          <div id="warnList"></div>
        </div>
        <div class="section" id="cleanSection" style="display:none">
          <div class="clean">✓ Lint passed — this library is ready to import into Fusion 360 Electronics.</div>
        </div>
      </div>
    </div>
  </main>
  <footer>standalone Rust — KiCad → EAGLE .lbr, no gallia, no Node, no local EAGLE</footer>

  <div id="tt"></div>
  <div id="toast"></div>

  <script>
    (function(){
      const post = (level,args)=>{ try{ navigator.sendBeacon('console', JSON.stringify({level,msg:args.map(a=>{try{return typeof a==='string'?a:JSON.stringify(a)}catch{return String(a)}}).join(' ')})); }catch(e){} };
      ['log','warn','error','info'].forEach(l=>{ const o=console[l].bind(console); console[l]=(...a)=>{ post(l,a); o(...a); }; });
      window.addEventListener('error', e=> post('error',[e.message+' @'+(e.filename||'')+':'+e.lineno]));
    })();

    (function(){
      const tt=document.getElementById('tt'); let timer=null, cur=null;
      function place(el){ const r=el.getBoundingClientRect(); tt.style.left='0px'; tt.style.top='0px'; tt.classList.add('show');
        const tr=tt.getBoundingClientRect(); let x=r.left+r.width/2-tr.width/2; let y=r.top-tr.height-8;
        x=Math.max(8,Math.min(x,innerWidth-tr.width-8)); if(y<8) y=r.bottom+8;
        tt.style.left=x+'px'; tt.style.top=y+'px'; }
      document.addEventListener('mouseover',e=>{ const el=e.target.closest('[data-tooltip]'); if(!el||el===cur) return; cur=el;
        clearTimeout(timer); timer=setTimeout(()=>{ tt.textContent=el.getAttribute('data-tooltip'); place(el); },500); });
      document.addEventListener('mouseout',e=>{ if(e.target.closest('[data-tooltip]')===cur){ clearTimeout(timer); tt.classList.remove('show'); cur=null; } });
    })();

    function esc(s){ return String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
    function statHtml(n,k,tip){ return '<div class="stat" data-tooltip="'+esc(tip)+'"><div class="n">'+n+'</div><div class="k">'+k+'</div></div>'; }
    function issueHtml(cls,o){ return '<div class="issue '+cls+'"><span class="code">'+esc(o.code)+'</span><span>'+esc(o.message)+'</span></div>'; }

    let lastHash='';
    async function pollState(){
      try{
        const r=await fetch('state',{cache:'no-store'}); const s=await r.json();
        setConn(true);
        const hash=JSON.stringify(s);
        if(hash!==lastHash){ lastHash=hash; render(s); }
      }catch(e){ setConn(false); }
    }
    function setConn(live){ const c=document.getElementById('conn'); c.classList.toggle('live',live); document.getElementById('connText').textContent=live?'live':'offline'; }
    function render(s){
      const has=!!s.hasContent;
      document.getElementById('empty').style.display=has?'none':'';
      document.getElementById('content').style.display=has?'':'none';
      document.getElementById('subjectName').textContent=has?s.libraryName:'—';
      document.getElementById('subjectMeta').textContent=has?(s.lintPassed?'lint pass':((s.errors||[]).length+' err')):'';
      document.getElementById('sendto').style.display=has?'inline-flex':'none';
      if(!has) return;

      document.getElementById('libName').textContent=s.libraryName;
      const badge=document.getElementById('badge'), bt=document.getElementById('badgeText');
      badge.className='badge '+(s.lintPassed?'pass':'fail');
      bt.textContent=s.lintPassed?'Lint passed':(((s.errors||[]).length)+' error'+(((s.errors||[]).length)===1?'':'s'));
      badge.setAttribute('data-tooltip', s.lintPassed
        ? 'All Fusion-360 lint checks passed — symbols, packages and a complete deviceset with pin→pad connects are present.'
        : 'Lint failed — Fusion 360 would reject or mis-import this library. Fix the errors below and re-generate.');

      document.getElementById('counts').innerHTML =
        statHtml(s.symbolCount,'symbols','Schematic symbol definitions in the library (EAGLE layer 94).')+
        statHtml(s.packageCount,'packages','PCB footprint/package definitions (pads + silkscreen).')+
        statHtml(s.devicesetCount,'devicesets','Devicesets bind a symbol to a package with pin→pad connects — what Fusion actually places.')+
        statHtml(s.pinCount,'pins','Total schematic pins across all symbols.')+
        statHtml(s.padCount,'pads','Total SMD/through-hole pads across all packages.');

      const errs=s.errors||[], warns=s.warnings||[];
      const eS=document.getElementById('errSection'), wS=document.getElementById('warnSection'), cS=document.getElementById('cleanSection');
      eS.style.display=errs.length?'':'none';
      document.getElementById('errList').innerHTML=errs.map(o=>issueHtml('e',o)).join('');
      wS.style.display=warns.length?'':'none';
      document.getElementById('warnList').innerHTML=warns.map(o=>issueHtml('w',o)).join('');
      cS.style.display=(!errs.length&&!warns.length)?'':'none';
    }
    setInterval(pollState,1000); pollState();

    // ── Send to desktop EDA tools ──
    let toastTimer=null;
    function showToast(msg,kind){ const t=document.getElementById('toast'); t.textContent=msg; t.className='show '+(kind||''); clearTimeout(toastTimer); toastTimer=setTimeout(()=>t.className=t.className.replace('show','').trim(),4200); }
    document.getElementById('sendto').addEventListener('click',async e=>{
      const b=e.target.closest('.sbtn'); if(!b) return;
      const target=b.dataset.target, label=b.textContent;
      const all=[...document.querySelectorAll('.sbtn')]; all.forEach(x=>x.disabled=true);
      showToast('Sending to '+label+'…');
      try{
        const r=await fetch('deliver',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({target})});
        const j=await r.json();
        if(j.needsBridge){ showBridgeModal(j); }
        else if(j.ok){ showToast('✓ '+(j.message||('Delivered to '+label)),'ok'); }
        else { showToast('✕ '+(j.message||j.error||('Could not deliver to '+label)),'err'); }
      }catch(err){ showToast('✕ Delivery failed: '+err,'err'); }
      finally{ all.forEach(x=>x.disabled=false); }
    });
    function showBridgeModal(j){
      let m=document.getElementById('bridgeModal');
      if(!m){ m=document.createElement('div'); m.id='bridgeModal'; document.body.appendChild(m); }
      m.innerHTML='<div class="bm-card"><div class="bm-icon">🌉</div>'
        +'<div class="bm-title">Build the '+(j.target||'')+' bridge</div>'
        +'<div class="bm-body">'+(j.message||'')+'</div>'
        +'<div class="bm-actions"><a class="bm-go" href="'+(j.contribute||'https://wiki.adom.inc')+'" target="_blank" rel="noopener">Contribute on the wiki →</a>'
        +'<button class="bm-dismiss">Got it</button></div></div>';
      m.classList.add('show');
      m.querySelector('.bm-dismiss').onclick=()=>m.classList.remove('show');
      m.onclick=e=>{if(e.target===m)m.classList.remove('show')};
    }

    async function evalLoop(){
      try{
        const r=await fetch('eval/pending',{cache:'no-store'}); const j=await r.json();
        if(j&&j.id){
          let result, err=null;
          try{ result=await (async()=>{ return eval(j.code); })(); }catch(e){ err=String(e&&e.stack||e); }
          await fetch('eval/'+j.id+'/result',{method:'POST',headers:{'Content-Type':'application/json'},
            body:JSON.stringify({result: err?undefined:safe(result), error:err})});
        }
      }catch(e){}
    }
    function safe(v){ try{ JSON.stringify(v); return v; }catch{ return String(v); } }
    setInterval(evalLoop,400); evalLoop();
  </script>
</body>
</html>