<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>adom-layout-viewer</title>
<style>
  /* Canonical Adom design tokens (adom-ui-design). Legacy names kept as aliases.
     Primary accent #00b8b0; #00e6dc is the bright hover variant. */
  :root { color-scheme: dark;
    --bg:#0d1117; --panel:#161b22; --elevated:#1c2128; --overlay:#21262d; --edge:#30363d;
    --ink:#e6edf3; --mut:#8b949e; --acc:#00b8b0; --acc2:#00e6dc;
    --green:#3fb950; --red:#f85149; --yellow:#d29922; --hl:#4ff0e6;
    --line:var(--edge); --txt:var(--ink); --muted:var(--mut); --accent:var(--acc); }
  * { box-sizing: border-box; }
  /* Three fonts by role: body Satoshi, headers Familjen Grotesk, data JetBrains Mono. */
  html, body { margin: 0; height: 100%; background: var(--bg); color: var(--txt); font: 13px/1.45 'Satoshi', -apple-system, 'Segoe UI', Roboto, sans-serif; overflow: hidden; }
  #svgbox { position: absolute; inset: 0; cursor: grab; }
  #svgbox:active { cursor: grabbing; }
  #svgbox svg { display: block; }
  .empty { position: absolute; inset: 0; display: grid; place-items: center; color: var(--muted); }

  /* overlay is invisible until hovered / net-highlighted; only pads+vias+traces take pointer events.
     pointer-events:FILL (not all) so the hit area is the pad SHAPE only — `all` would include the
     phantom 1mm default stroke and make the hit zone ~2.5mm wide. */
  #svgbox svg .adom-overlay .adom-pad .pad-body, #svgbox svg .adom-overlay .adom-via .pad-body { pointer-events: fill; cursor: pointer; }
  #svgbox svg .adom-overlay .ov-trace { pointer-events: stroke; cursor: pointer; }
  /* fills are clickable (select the net) but never hovered — hover only targets pads/vias */
  #svgbox svg .adom-overlay .ov-zone { pointer-events: fill; cursor: pointer; }
  /* ── hover: gentle white outline on the single pad/via ── */
  #svgbox svg .adom-pad.pad-hl .pad-body { stroke: #fff !important; stroke-width: .07; paint-order: stroke; }
  #svgbox svg .adom-via.pad-hl .pad-body { stroke: #fff !important; stroke-width: .05; }
  /* cross-probe: the part selected from the schematic view — bright accent outline
     on all of its pads so the whole footprint reads as "this component". */
  #svgbox svg .adom-pad.comp-xhl .pad-body { stroke: var(--acc2) !important; stroke-width: .14 !important; paint-order: stroke; }
  /* ── click net: dim every base layer; show the selected net's copper EXACTLY as on the
        PCB (solid traces w/ round joins, real pads w/ drill holes), just in focus. No
        outlines, no brightness boost, no transition (that caused the shrink). ── */
  #svgbox svg.dim .adom-flip > g[class*="adom-layer-"] { opacity: .16 !important; }
  #svgbox svg .adom-overlay .ov-trace.hl { stroke: var(--hlc) !important; }
  #svgbox svg .adom-overlay .ov-zone.hl { fill: var(--hlc) !important; fill-opacity: .3 !important; stroke: none !important; }
  #svgbox svg .adom-overlay .adom-pad.hl .pad-body, #svgbox svg .adom-overlay .adom-via.hl .pad-body { fill: var(--hlc) !important; }
  #svgbox svg .adom-overlay .adom-pad.hl .pad-hole, #svgbox svg .adom-overlay .adom-via.hl .pad-hole { fill: #161b22 !important; }

  /* ── chrome ── */
  .panel { position: absolute; background: rgba(22,27,34,.82); backdrop-filter: blur(8px); border: 1px solid var(--line); border-radius: 10px; }
  /* single right-side panel: Layers section on top, Nets below */
  #side { top: 12px; right: 12px; width: 212px; max-height: 94vh; padding: 10px 12px; display: flex; flex-direction: column; }
  #side h1 { margin: 0 0 8px; font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); font-weight: 600; }
  .sec { margin-bottom: 10px; min-height: 0; display: flex; flex-direction: column; }
  .sec-h { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); font-weight: 600; margin: 2px 0 5px; border-bottom: 1px solid var(--line); padding-bottom: 3px; }
  #layers { max-height: 42vh; overflow-y: auto; }
  #netlist { max-height: 30vh; overflow-y: auto; }
  .lyr { display: flex; align-items: center; gap: 7px; padding: 2px 0; cursor: pointer; user-select: none; }
  .lyr input { accent-color: var(--accent); }
  #fitbtn { margin-top: 4px; width: 100%; background: #17231c; color: var(--txt); border: 1px solid var(--line); border-radius: 7px; padding: 5px; cursor: pointer; }
  #fitbtn:hover { border-color: var(--accent); }
  #sidebtns { display: flex; align-items: center; gap: 4px; margin-bottom: 8px; }
  #sidebtns .lbl { color: var(--muted); font-size: 11px; margin-right: auto; }
  .sidebtn { background: #17231c; color: var(--muted); border: 1px solid var(--line); border-radius: 6px; padding: 2px 8px; cursor: pointer; font-size: 11px; }
  .sidebtn.on { background: var(--accent); color: #111; border-color: var(--accent); font-weight: 600; }
  .lyr-name { cursor: pointer; }
  .lyr-name:hover { color: var(--accent); text-decoration: underline; }
  /* the copper layer currently raised to the front (click its name again to reset) */
  .lyr.raised .lyr-name { color: var(--accent); font-weight: 600; }
  .lyr.raised .lyr-name::after { content: " \2191"; }
  .netrow { padding: 3px 7px; border-radius: 5px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums; }
  .netrow:hover { background: #1c2a22; color: var(--hl); }
  .netrow.sel { background: var(--accent); color: #06232a; font-weight: 600; }
  .netrow.sel:hover { color: #06232a; }

  #stat { bottom: 12px; left: 12px; padding: 6px 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
  #netchip { bottom: 12px; left: 50%; transform: translateX(-50%); padding: 6px 13px; display: none; }
  #netchip b { color: var(--accent); text-transform: uppercase; font-size: 10px; letter-spacing: .06em; margin-right: 4px; }
  .muted { color: var(--muted); }

  /* ── component info card ── */
  #card { position: fixed; z-index: 9999; display: none; min-width: 180px; max-width: 260px; padding: 10px 12px; pointer-events: none; }
  .ci-h { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin-bottom: 6px; padding-bottom: 6px; border-bottom: 1px solid var(--line); }
  .ci-ref { font-weight: 700; color: var(--accent); font-size: 14px; }
  .ci-val { color: var(--txt); }
  .ci-row { display: flex; justify-content: space-between; gap: 10px; padding: 1px 0; }
  .ci-k { color: var(--muted); }
  .ci-v { text-align: right; word-break: break-word; }
  .ci-enrich { margin-top: 7px; padding-top: 7px; border-top: 1px solid var(--line); }
  .pills { display: flex; flex-wrap: wrap; gap: 5px; }
  .pill { background: #17231c; border: 1px solid var(--line); border-radius: 999px; padding: 1px 8px; font-size: 11px; }
  .pill.ok { color: #57d97e; border-color: #2c5a3a; }
  .pill.no { color: #e08a8a; border-color: #5a2c2c; }
  .pill.tier { color: #f2d060; }
  .pill.pop { color: #cba6ff; }
  .ci-wiki { display: inline-block; margin-top: 6px; color: #6bb3ff; text-decoration: none; }
  #side h1, .sec-h { font-family: 'Familjen Grotesk', -apple-system, sans-serif; }

  /* ── canonical Adom 44px three-zone header (brand · subject · actions) ── */
  header.app { display: none; position: fixed; top: 0; left: 0; right: 0; height: 44px; z-index: 50;
    grid-template-columns: 1fr auto 1fr; align-items: center; gap: 12px; padding: 0 14px;
    background: var(--panel); border-bottom: 1px solid var(--edge); }
  body.chrome header.app { display: grid; }
  body.chrome #svgbox { top: 44px; }
  body.chrome #side { top: 56px; }
  header.app .brand { display: flex; align-items: center; gap: 8px; min-width: 0; position: relative; cursor: default; padding-right: 14px; border-right: 1px solid var(--edge); }
  header.app .brand svg { width: 16px; height: 16px; color: var(--ink); flex: 0 0 16px; }
  header.app .app-name { font-family: 'Familjen Grotesk', -apple-system, sans-serif; font-weight: 600; font-size: 13px; color: var(--acc); white-space: nowrap; }
  header.app .app-sub { color: var(--mut); font-size: 12px; white-space: nowrap; }
  header.app .app-sub::before { content: "·"; margin: 0 6px; color: var(--mut); }
  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: 44vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  header.app .subject-meta { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 11px; color: var(--mut); white-space: nowrap; }
  header.app .actions { justify-self: end; display: flex; align-items: center; gap: 8px; }
  header.app .actions button { background: var(--elevated); color: var(--ink); border: 1px solid var(--edge); border-radius: 7px; width: 30px; height: 26px; cursor: pointer; display: grid; place-items: center; }
  header.app .actions button:hover { border-color: var(--acc); }
  header.app .actions button svg { width: 15px; height: 15px; }
  header.app .appinfo { position: absolute; top: calc(100% + 6px); left: 0; min-width: 220px; background: var(--overlay); border: 1px solid var(--edge);
    border-radius: 10px; box-shadow: 0 12px 34px #000a; padding: 12px 14px; font-size: 12px; opacity: 0; visibility: hidden; transition: opacity .18s ease; pointer-events: none; z-index: 60; }
  header.app .brand:hover .appinfo { opacity: 1; visibility: visible; pointer-events: auto; transition-delay: .6s; }
  header.app .appinfo .ai-row { display: flex; justify-content: space-between; gap: 16px; padding: 2px 0; }
  header.app .appinfo .ai-k { color: var(--mut); }
  header.app .appinfo .ai-v { font-family: 'JetBrains Mono', ui-monospace, monospace; }
  header.app .appinfo a { color: var(--acc); text-decoration: none; }
  header.app .appinfo a:hover { text-decoration: underline; }

  /* ── toasts: transient feedback, bottom-center; also the AI's live channel ── */
  #toasts { position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%); z-index: 3000; display: flex; flex-direction: column-reverse; gap: 8px; align-items: center; pointer-events: none; }
  .toast { pointer-events: auto; min-width: 200px; max-width: min(420px,90vw); display: flex; align-items: center; gap: 10px; padding: 10px 13px; border-radius: 10px;
    background: var(--overlay); border: 1px solid var(--edge); color: var(--ink); font-size: 13px; box-shadow: 0 10px 30px #000a; cursor: pointer; animation: toastin .18s ease; }
  @keyframes toastin { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
  .toast.out { opacity: 0; transform: translateY(8px); transition: opacity .2s ease, transform .2s ease; }
  .toast .ti { flex: 0 0 16px; width: 16px; height: 16px; }
  .toast.success .ti { color: var(--green); } .toast.success { border-color: #2c5a3a; }
  .toast.error .ti { color: var(--red); } .toast.error { border-color: #5a2c2c; }
  .toast.info .ti { color: var(--acc); }
  .toast .tmsg { flex: 1; min-width: 0; }

  /* ── tooltips: body-appended fixed div, 600ms reveal, clamped both axes ── */
  #tt { position: fixed; z-index: 99999; max-width: 260px; background: var(--overlay); color: var(--ink); border: 1px solid var(--edge);
    border-radius: 7px; padding: 6px 9px; font-size: 12px; line-height: 1.4; box-shadow: 0 8px 22px #000b; pointer-events: none;
    opacity: 0; transform: translateY(3px); transition: opacity .18s ease, transform .18s ease; text-transform: none; }
  #tt.show { opacity: 1; transform: none; }
  @media (hover: none), (pointer: coarse) { #tt { display: none; } }
</style>
</head>
<body>
  <header class="app">
    <div class="brand">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="1.5"/><path d="M7 8h4v4H7zM14 14h3M15.5 8v4"/><circle cx="17" cy="8" r="1.1"/></svg>
      <span class="app-name">PCB Viewer</span><span class="app-sub">interactive layout</span>
      <div class="appinfo">
        <div class="ai-row"><span class="ai-k">version</span><span class="ai-v" id="ai-ver">-</span></div>
        <div class="ai-row"><span class="ai-k">engine</span><span class="ai-v">KiCad · Altium · Fusion</span></div>
        <div class="ai-row"><span class="ai-k">source</span><a href="https://wiki.adom.inc/adom/adom-layout-viewer" target="_blank" rel="noopener">wiki page ↗</a></div>
      </div>
    </div>
    <div class="subject"><span class="subject-name" id="hdr-name"></span><span class="subject-meta" id="hdr-meta"></span></div>
    <div class="actions">
      <button id="hdr-fit" data-tip="Fit the whole board to the view"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5"/></svg></button>
    </div>
  </header>
  <div id="svgbox"><div class="empty">loading…</div></div>

  <div id="side" class="panel">
    <div class="sec"><div class="sec-h">Layers</div><div id="layers"></div></div>
    <div class="sec"><div class="sec-h">Nets</div><div id="netlist"></div></div>
    <button id="fitbtn" data-tip="Zoom to fit the entire board">Fit</button>
  </div>

  <div id="stat" class="panel"></div>
  <div id="netchip" class="panel"></div>
  <div id="card" class="panel"></div>
  <div id="toasts"></div>

  <script src="viewer.js"></script>
</body>
</html>