← Commit history

interactive embeds

Caleb ·16a3f780c5 ·1mo ago ·parent c748f98
1 file changed +171 diff truncated
viewer/layout.htmladded+171
@@ -0,0 +1,683 @@+<!doctype html>+<html lang="en">+<head>+<meta charset="utf-8" />+<meta name="viewport" content="width=device-width, initial-scale=1" />+<title>adom-2dboard</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-2dboard" 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>+