app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Drive your own KiCad from an AI, in the background: open every editor, read the whole board as data (the EDA Bridge Standard board model), stack-up, part heights, netlists, place, route, pour, DRC and exports.
master
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>KiCad Bridge</title>
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<style>
:root{
--bg:#0d1117;--surface:#161b22;--elevated:#1c2128;--overlay:#21262d;--border:#30363d;
/* aliases: rules below were written against the dash-header names (--line/--muted/--teal)
while this sheet defines --border/--text-2/--accent; both must resolve (wiki #66) */
--line:#30363d;--muted:#8b949e;--teal:#00b8b1;--sans:'Familjen Grotesk',sans-serif;
--text:#e6edf3;--text-body:#c9d1d9;--text-2:#8b949e;
--accent:#00b8b1;--accent-hover:#00d4cb;--on-accent:#05221f;--green:#3fb950;--red:#f85149;--yellow:#d29922;--blue:#64abff;
--brand-heading:'Familjen Grotesk',sans-serif;--brand-body:'Satoshi',sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
@font-face{font-family:'Familjen Grotesk';src:url('https://adom.inc/fonts/FamiljenGrotesk-SemiBold.woff2') format('woff2');font-weight:600}
@font-face{font-family:'Satoshi';src:url('https://adom.inc/fonts/Satoshi-Regular.woff2') format('woff2');font-weight:400}
@font-face{font-family:'Satoshi';src:url('https://adom.inc/fonts/Satoshi-Medium.woff2') format('woff2');font-weight:500}
body{background:var(--bg);color:var(--text-body);font:15px/1.5 'Satoshi',sans-serif;min-height:100vh}
/* House scrollbar everywhere (scrollbars sub-skill, applied verbatim per the
dock thread 2026-08-23: match the wiki's slim dark pill on every surface). */
*{scrollbar-width:thin;scrollbar-color:rgba(139,148,158,.38) transparent}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:rgba(139,148,158,.32);border-radius:999px;border:2px solid transparent;background-clip:padding-box}
::-webkit-scrollbar-thumb:hover{background:rgba(0,184,177,.55);background-clip:padding-box}
::-webkit-scrollbar-corner{background:transparent}
body{overflow-x:hidden} /* the page never scrolls horizontally */
/* FLUID, and the FLEET GUTTER. No max-width: a dashboard fills its window like
a native app. The 16px horizontal padding is the contract (newbie-panel
2.3.0): the panel's border box must equal documentElement.clientWidth - 32,
so every app's panel is the same width at the same pane and the 700px
two-column threshold is crossed by all of them together. This rule was lost
in an earlier CSS deletion, which left the panel full-bleed at 734 and the
page content hugging the left edge - visible in my own frame, and I did not
look hard enough at it. */
main{margin:0;padding:20px 16px 84px}
header{height:44px;display:flex;align-items:center;gap:12px;padding:0 16px;margin:0;border-radius:0;background:var(--surface);border-bottom:1px solid var(--border);overflow:hidden}
/* The header was the ONLY thing overflowing narrow panes: measured at a real
236px surface the nowrap brand + target chip + four ghost buttons pushed
scrollWidth to 410. Everything below the header already fitted. So the bar
shrinks: brand ellipsises, the chip caps and ellipsises, the buttons never
shrink (they are the controls), and the subtitle drops out first. */
/* wiki #66: the visual rules for the header were deleted at some point (the orphaned
.iconbtn:focus-visible rule outlived them); UA-default white boxes were the result.
Values are dash-header's numbers (John, 2026-08-24). */
.brand{display:flex;align-items:center;gap:8px;font-family:'Familjen Grotesk',sans-serif;font-weight:600;font-size:15px;letter-spacing:-.01em;color:var(--text);white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis}
.brand svg{flex:none;width:18px;height:18px;stroke:var(--text)}
.brand .sub{font-size:13px;font-weight:400;color:var(--muted);margin-left:8px}
.vsep{width:1px;height:20px;background:var(--border);flex:none}
.target-chip{display:inline-flex;align-items:center;gap:6px;font:12px 'JetBrains Mono',ui-monospace,monospace;color:var(--text-body);background:var(--elevated);border:1px solid var(--border);border-radius:999px;padding:3px 10px;white-space:nowrap}
.target-chip .tdot{width:8px;height:8px;border-radius:50%;background:var(--muted);flex:none}
.target-chip[data-state="ok"] .tdot{background:var(--green)}
.target-chip[data-state="bad"] .tdot{background:var(--red)}
.actions{margin-left:auto;display:flex;gap:6px;flex:none}
.iconbtn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;background:none;border:1px solid transparent;border-radius:8px;color:var(--text-2);cursor:pointer;padding:0;transition:background .15s ease,border-color .15s ease,transform .06s ease}
.iconbtn svg{width:18px;height:18px}
.iconbtn:hover{background:var(--elevated);border-color:var(--border);color:var(--accent)}
.iconbtn:active{transform:scale(.9);background:var(--overlay)}
.iconbtn[aria-pressed="true"]{color:var(--accent)}
.actions{flex:none}
.target-chip{min-width:0;max-width:38vw;overflow:hidden}
.target-chip #chipName{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Header responsiveness (the ONLY overflow source at narrow panes: the
nowrap brand + chip + buttons pushed a 236px surface to 410px). The module
owns the panel; this is ours. */
@media (max-width:700px){
.brand .sub{display:none}
.target-chip{max-width:30vw}
}
@media (max-width:420px){
header{gap:8px;padding:0 10px}
.vsep{display:none}
.target-chip{max-width:26vw}
}
/* status-leds pill row */
.leds{display:flex;flex-wrap:wrap;gap:8px}
.led-pill{display:inline-flex;align-items:center;gap:7px;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:4px 12px;font-size:12.5px;cursor:pointer;color:var(--text-body);transition:border-color .15s ease}
.led-pill:hover{border-color:var(--teal)}
.led-pill:active{transform:translateY(1px)}
.led-pill .dot{width:8px;height:8px;border-radius:50%;background:var(--muted);flex:none}
.led-pill[data-state=ok] .dot{background:var(--led-green)}
.led-pill[data-state=busy] .dot{background:var(--led-amber);animation:ledpulse 1.1s ease-in-out infinite}
.led-pill[data-state=stuck] .dot{background:var(--led-amber)}
.led-pill[data-state=bad] .dot{background:var(--led-red)}
.led-pill .st{color:var(--muted)}
.led-detail-pop{position:fixed;z-index:99998;max-width:360px;background:var(--overlay);border:1px solid var(--line);border-radius:10px;padding:12px 14px;font-size:13px;color:var(--text-body);box-shadow:0 8px 24px rgba(0,0,0,.4)}
.led-detail-pop h4{font:600 13px var(--sans);color:var(--text);margin:0 0 6px}
.led-detail-pop .hint{color:var(--muted);margin-top:6px;font-size:12.5px}
.led-detail-pop .winlist{font:12px var(--mono);color:var(--muted);margin-top:6px;white-space:pre-wrap}
/* dash-demo toolbar */
#demoToolbar{position:fixed;left:50%;bottom:56px;transform:translateX(-50%);z-index:99998;display:flex;align-items:center;gap:6px;background:var(--overlay);border:1px solid var(--line);border-radius:999px;padding:6px 12px;box-shadow:0 8px 24px rgba(0,0,0,.45)}
.dt-btn{background:none;border:1px solid transparent;border-radius:8px;color:var(--text);font-size:15px;padding:4px 8px;cursor:pointer;transition:background .15s ease,border-color .15s ease,transform .06s ease}
.dt-btn:hover{background:var(--surface2);border-color:var(--line)}
.dt-btn:active{transform:translateY(1px) scale(.95)}
.dt-btn:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.dt-sep{width:1px;height:18px;background:var(--line)}
.dt-step{font:12px var(--mono);color:var(--muted);padding:0 4px}
#dtMute.muted{opacity:.45}
#dtExit{color:var(--led-red)}
/* status-leds pill row */
.leds{display:flex;flex-wrap:wrap;gap:8px}
.led-pill{display:inline-flex;align-items:center;gap:7px;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:4px 12px;font-size:12.5px;cursor:pointer;color:var(--text-body);transition:border-color .15s ease}
.led-pill:hover{border-color:var(--teal)}
.led-pill:active{transform:translateY(1px)}
.led-pill .dot{width:8px;height:8px;border-radius:50%;background:var(--muted);flex:none}
.led-pill[data-state=ok] .dot{background:var(--led-green)}
.led-pill[data-state=busy] .dot{background:var(--led-amber);animation:ledpulse 1.1s ease-in-out infinite}
.led-pill[data-state=stuck] .dot{background:var(--led-amber)}
.led-pill[data-state=bad] .dot{background:var(--led-red)}
.led-pill .st{color:var(--muted)}
.led-detail-pop{position:fixed;z-index:99998;max-width:360px;background:var(--overlay);border:1px solid var(--line);border-radius:10px;padding:12px 14px;font-size:13px;color:var(--text-body);box-shadow:0 8px 24px rgba(0,0,0,.4)}
.led-detail-pop h4{font:600 13px var(--sans);color:var(--text);margin:0 0 6px}
.led-detail-pop .hint{color:var(--muted);margin-top:6px;font-size:12.5px}
.led-detail-pop .winlist{font:12px var(--mono);color:var(--muted);margin-top:6px;white-space:pre-wrap}
/* dash-demo toolbar */
#demoToolbar{position:fixed;left:50%;bottom:56px;transform:translateX(-50%);z-index:99998;display:flex;align-items:center;gap:6px;background:var(--overlay);border:1px solid var(--line);border-radius:999px;padding:6px 12px;box-shadow:0 8px 24px rgba(0,0,0,.45)}
.dt-btn{background:none;border:1px solid transparent;border-radius:8px;color:var(--text);font-size:15px;padding:4px 8px;cursor:pointer;transition:background .15s ease,border-color .15s ease,transform .06s ease}
.dt-btn:hover{background:var(--surface2);border-color:var(--line)}
.dt-btn:active{transform:translateY(1px) scale(.95)}
.dt-btn:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.dt-sep{width:1px;height:18px;background:var(--line)}
.dt-step{font:12px var(--mono);color:var(--muted);padding:0 4px}
#dtMute.muted{opacity:.45}
#dtExit{color:var(--led-red)}
/* ── newbie-panel (adom-ui-design 1.22.1) ──────────────────────────────────
ONE full-width band, SIBLING of the app body, two columns: demo left,
prompts TABLE right. Pills were retired: six phrases of different lengths in
chips read as a ragged staircase. NO max-width anywhere - a dashboard is an
app surface, not an article; prose is capped, the layout never is. */
justify-content:center;background:none;border:1px solid transparent;border-radius:6px;
/* clamp(), NOT minmax() (newbie-panel 1.32.0, found by parts-search): a
minmax() track resolves to its MAX whenever the neighbouring 1fr can shrink,
and the content side sets min-width:0 (it must, or a nowrap phrase blows the
grid out). So minmax(180px,220px) was a HARD 220px at every width - the
narrow-end flex it looked like it had was unreachable by construction. A
container percentage genuinely tracks the pane. */
/* 700px (adom-ui-design, 2026-08-25). Was 760, then 620, now 700: his panes
measure 632, 744 and 851 SIMULTANEOUSLY - there is no single pane width -
and at 632 the two-column layout truncated the very qualifier the strip
exists to teach. 700 puts 632 in one column and 744/851 in two.
which sits BELOW 760, so the two-column panel never rendered in the pane it
was designed for: the app looked compliant and every screenshot he saw was
the collapsed fallback. A breakpoint bug is invisible to anyone whose test
widths straddle it, so 744 is now in my matrix. */
border:none;border-radius:8px;padding:10px 18px;font:600 14px var(--sans);cursor:pointer;
/* prompts TABLE: one left edge, one row height, so the eye reads phrases not boxes */
/* the row is a grid so the ACTIONS TRACK THE PHRASE, not the window edge: a
Copy button 2000px from its text on a wide monitor is the bug this fixes */
background:none;border:1px solid transparent;border-radius:6px;color:var(--muted);cursor:pointer;
@media (pointer:coarse){.ibtn{padding:8px;width:44px;height:44px}}
/* status-leds pill row */
.leds{display:flex;flex-wrap:wrap;gap:8px}
.led-pill{display:inline-flex;align-items:center;gap:7px;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:4px 12px;font-size:12.5px;cursor:pointer;color:var(--text-body);transition:border-color .15s ease}
.led-pill:hover{border-color:var(--teal)}
.led-pill:active{transform:translateY(1px)}
.led-pill .dot{width:8px;height:8px;border-radius:50%;background:var(--muted);flex:none}
.led-pill[data-state=ok] .dot{background:var(--led-green)}
.led-pill[data-state=busy] .dot{background:var(--led-amber);animation:ledpulse 1.1s ease-in-out infinite}
.led-pill[data-state=stuck] .dot{background:var(--led-amber)}
.led-pill[data-state=bad] .dot{background:var(--led-red)}
.led-pill .st{color:var(--muted)}
.led-detail-pop{position:fixed;z-index:99998;max-width:360px;background:var(--overlay);border:1px solid var(--line);border-radius:10px;padding:12px 14px;font-size:13px;color:var(--text-body);box-shadow:0 8px 24px rgba(0,0,0,.4)}
.led-detail-pop h4{font:600 13px var(--sans);color:var(--text);margin:0 0 6px}
.led-detail-pop .hint{color:var(--muted);margin-top:6px;font-size:12.5px}
.led-detail-pop .winlist{font:12px var(--mono);color:var(--muted);margin-top:6px;white-space:pre-wrap}
/* dash-demo toolbar */
#demoToolbar{position:fixed;left:50%;bottom:56px;transform:translateX(-50%);z-index:99998;display:flex;align-items:center;gap:6px;background:var(--overlay);border:1px solid var(--line);border-radius:999px;padding:6px 12px;box-shadow:0 8px 24px rgba(0,0,0,.45)}
.dt-btn{background:none;border:1px solid transparent;border-radius:8px;color:var(--text);font-size:15px;padding:4px 8px;cursor:pointer;transition:background .15s ease,border-color .15s ease,transform .06s ease}
.dt-btn:hover{background:var(--surface2);border-color:var(--line)}
.dt-btn:active{transform:translateY(1px) scale(.95)}
.dt-btn:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.dt-sep{width:1px;height:18px;background:var(--line)}
.dt-step{font:12px var(--mono);color:var(--muted);padding:0 4px}
#dtMute.muted{opacity:.45}
#dtExit{color:var(--led-red)}
/* "Say it": the first thing on the page (John, 2026-08-23: teach users how to ASK for the app by its noun, first and foremost). */
h2{font-family:'Familjen Grotesk',sans-serif;font-size:17px;color:var(--text);margin:26px 0 12px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:12px}
.card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:14px}
.led-row{display:flex;align-items:center;gap:10px}
.led{width:12px;height:12px;border-radius:999px;background:var(--text-2);flex:none;transition:background .15s ease}
.led.on{background:var(--green);box-shadow:0 0 8px rgba(63,185,80,.5)}
.led.off{background:var(--red)}
.led.warn{background:var(--yellow)}
.led.dim{background:var(--text-2)}
.led.busy{background:var(--accent);animation:ledpulse 1.1s ease-in-out infinite}
@keyframes ledpulse{0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(0,184,177,.55)}
50%{opacity:.55;box-shadow:0 0 10px 3px rgba(0,184,177,.25)}}
.card.busy{border-color:rgba(0,184,177,.45)}
.led-label{font-weight:500;color:var(--text)}
.led-detail{font-size:12.5px;color:var(--text);margin-top:6px}
.led-sub{font-size:12.5px;color:var(--text-2);margin-top:6px}
.btn{display:inline-flex;align-items:center;gap:8px;min-height:36px;padding:0 14px;border-radius:8px;border:1px solid var(--border);background:var(--elevated);color:var(--text);font:500 14px 'Satoshi',sans-serif;cursor:pointer;transition:background .15s ease,border-color .15s ease,transform .06s ease}
.btn:hover{background:var(--overlay);border-color:#4b5563}
.btn:active{transform:translateY(1px) scale(.98);background:var(--surface)}
.btn.primary{background:var(--accent);border-color:var(--accent);color:#05221f}
.btn.primary:hover{background:var(--accent-hover)}
.btn.primary:active{background:#009d97;border-color:#009d97;transform:translateY(1px) scale(.98)}
.btn:disabled{opacity:.5;cursor:not-allowed}
.btn:focus-visible,.iconbtn:focus-visible,.tpill:focus-visible,.toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.btnrow{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px}
.prompt{display:flex;align-items:center;gap:10px;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:10px 12px;margin-bottom:8px;transition:border-color .15s ease}
.prompt:hover{border-color:#4b5563}
input:focus-visible{outline:none;border-color:var(--accent) !important}
.prompt code{font:13px 'JetBrains Mono',monospace;color:var(--text-body);flex:1;white-space:normal}
.copy{flex:none}
.bar{height:8px;background:var(--elevated);border:1px solid var(--border);border-radius:999px;overflow:hidden;margin-top:10px;display:none}
.bar>div{height:100%;width:0;background:var(--accent);transition:width .5s ease}
/* live-frame states (fusion timing contract): a non-measured estimate renders
DIMMED so a guess never looks as authoritative as a measurement; overdue or
waiting-on-a-human renders AMBER; percent:null renders an indeterminate
marquee, never a fake number. */
.sbar.dim>div{opacity:.45}
.sbar.amber>div{background:#d29922}
.sbar.indet>div{width:30%!important;animation:marquee 1.2s linear infinite}
@keyframes marquee{0%{margin-left:-30%}100%{margin-left:100%}}
.barlabel{font-size:12.5px;color:var(--text-2);margin-top:6px}
.scard{display:flex;flex-direction:column;gap:8px}
.scard .btn{width:100%;justify-content:center;text-align:center}
.slog{font:12px 'JetBrains Mono',monospace;color:var(--text-2);min-height:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sbar{height:6px;background:var(--elevated);border:1px solid var(--border);border-radius:999px;overflow:hidden;display:none}
.sbar>div{height:100%;width:0;background:var(--accent);transition:width .4s ease}
.sshot{border:1px solid var(--border);border-radius:8px;overflow:hidden;display:none}
.sshot img{width:100%;display:block}
.scard.err .slog{color:var(--red)}
.shots{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;margin-top:12px}
.shot{background:var(--elevated);border:1px solid var(--border);border-radius:8px;overflow:hidden}
.shot img{width:100%;display:block}
.shot .cap{font-size:11.5px;color:var(--text-2);padding:6px 8px}
#toasts{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);display:flex;flex-direction:column-reverse;gap:8px;z-index:400}
/* John, 2026-09-03: toasts were hidden under the demo transport pill; while the pill is up, toasts stack above it */
body.demo-running #toasts{bottom:112px;z-index:99999}
.toast{background:var(--surface);border:1px solid var(--line);border-left:3px solid var(--teal);
border-radius:8px;padding:10px 14px;color:var(--text);font-size:14px;box-shadow:0 8px 24px rgba(0,0,0,.4);
animation:toastin 180ms ease-out}
@keyframes toastin{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.toast.out{animation:toastout 140ms ease-in forwards}
@keyframes toastout{to{opacity:0}}
@media (prefers-reduced-motion:reduce){.toast,.toast.out{animation:none}}
.toast.ok{border-left-color:var(--led-green)}.toast.err{border-left-color:var(--led-red)}
dialog{margin:auto;background:var(--surface);color:var(--text-body);border:1px solid var(--border);border-radius:12px;padding:20px;max-width:520px;width:92vw}
/* The backdrop law (settings-layout skill): dim 20-30%, NEVER blur, one value
for every dialog in the app - settings often change the very thing behind
the modal and the user must be able to watch it change. */
dialog::backdrop{background:rgba(0,0,0,.25)}
/* ---- Settings modal: the Adom settings surface (settings-layout skill) ---- */
dialog.settings{width:min(660px,92vw);max-width:none;padding:0;overflow:hidden;border-radius:12px;background:var(--surface)}
dialog.settings:focus{outline:none}
.settings-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px 12px 18px;border-bottom:1px solid var(--border);cursor:move;user-select:none}
.settings-head h2{margin:0;font:600 15px/1.3 var(--brand-heading);color:var(--text);display:flex;align-items:center;gap:10px}
.settings-head .card-status{font-family:var(--brand-body)}
.settings-body{max-height:70vh;overflow:auto;padding:18px;scrollbar-gutter:stable}
.settings-card{background:var(--elevated);border:1px solid var(--border);border-left:3px solid color-mix(in srgb,var(--accent) 45%,transparent);border-radius:10px;padding:.9rem 1.1rem 1rem;margin-bottom:1rem}
.card-title{font:600 13px/1.3 var(--brand-heading);color:var(--text);display:flex;align-items:center;gap:10px;margin:0 0 .45rem}
.card-status{font-size:11px;color:var(--text-2);font-weight:400;font-family:var(--brand-body)}
.card-status.ok{color:var(--green)}
.card-desc{font-family:var(--brand-body);font-size:12px;line-height:1.55;color:var(--text-2);max-width:60ch;margin:0 0 .55rem}
.card-desc code{font:11px 'JetBrains Mono',monospace;background:rgba(110,118,129,.2);padding:1px 4px;border-radius:4px}
.divider{height:1px;background:var(--border);margin:.55rem 0}
.wrapper{padding:.5rem 0;display:flex;flex-direction:column;gap:.25rem}
.name{margin:.125rem 0;font-family:var(--brand-heading);font-size:.9375rem;font-weight:600}
.prefix{color:var(--text)}
.title{color:#8fd3d4}
.default-hint{font-family:var(--brand-body);font-size:.75rem;color:var(--text-2)}
.setting-shot{margin:0 0 .75rem}
.shot-pair{display:flex;flex-wrap:wrap;gap:.75rem}
.shot-pair .setting-shot{flex:1 1 272px;max-width:272px}
.setting-shot figcaption b{color:var(--text-body);font-weight:600}
.setting-shot img{display:block;max-width:100%;height:auto;border:1px solid var(--border);border-radius:8px;background:var(--surface)}
.setting-shot figcaption{margin-top:.35rem;font-family:var(--brand-body);font-size:.75rem;color:var(--text-2)}
.choice-set{border:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:.75rem}
.choice{flex:1 1 280px;display:flex;flex-direction:column;gap:.35rem;padding:.6rem;cursor:pointer;position:relative;background:var(--surface);border:1px solid var(--border);border-radius:8px;transition:border-color .15s ease,background .15s ease,transform .06s ease}
.choice input{position:absolute;opacity:0;pointer-events:none}
.choice img{display:block;width:100%;max-width:272px;height:auto;border-radius:4px;background:var(--bg);image-rendering:auto}
.choice-name{font-family:var(--brand-heading);font-size:.875rem;font-weight:600;color:var(--text)}
.choice-desc{font-family:var(--brand-body);font-size:.75rem;color:var(--text-2);line-height:1.5}
.choice:hover{border-color:var(--accent);background:var(--elevated)}
.choice:active{transform:translateY(1px) scale(.995)}
.choice[data-selected='true']{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}
.choice:focus-within{outline:2px solid var(--accent);outline-offset:2px}
.setting-row{display:flex;align-items:center;gap:10px}
.set-check-label{font-family:var(--brand-body);font-size:.875rem;color:var(--text-body)}
.toggle{background:none;border:none;padding:2px;cursor:pointer;flex-shrink:0;transition:transform .06s ease}
.toggle-track{display:block;width:32px;height:18px;background:var(--overlay);border:1px solid var(--border);border-radius:9px;position:relative;transition:background .15s ease,border-color .15s ease}
.toggle:hover .toggle-track{border-color:var(--accent)}
.toggle:active{transform:translateY(1px) scale(.97)}
.toggle.active .toggle-track{background:var(--accent);border-color:var(--accent)}
.toggle-thumb{position:absolute;top:2px;left:2px;width:12px;height:12px;background:#fff;border-radius:50%;transition:transform .18s ease,width .12s ease}
.toggle.active .toggle-thumb{transform:translateX(14px)}
.toggle:active .toggle-thumb{width:16px}
.toggle.active:active .toggle-thumb{transform:translateX(10px)}
.toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:10px}
.settings-footer{margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:.5rem}
.footer-note{font-family:var(--brand-body);font-size:.75rem;color:var(--text-2);margin:0;max-width:60ch}
.restore-btn{width:fit-content;display:inline-flex;align-items:center;gap:.4rem;padding:.4rem .85rem;background:transparent;border:1px solid var(--border);border-radius:.375rem;color:var(--text-body);font-family:var(--brand-heading);font-size:.8125rem;font-weight:600;cursor:pointer;transition:background .15s ease,border-color .15s ease,color .15s ease,transform .06s ease}
.restore-btn:hover{background:var(--elevated)}
.restore-btn:active{transform:translateY(1px) scale(.98)}
.restore-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.restore-btn.armed{color:var(--red);border-color:rgba(255,107,107,.5);background:rgba(255,107,107,.08)}
@media (max-width:720px){.meta{display:none}.grid{grid-template-columns:1fr 1fr}}
@media (max-width:480px){.grid{grid-template-columns:1fr}.brand span{display:none}.subject{display:none}.vsep{display:none}header{gap:8px}}
#tt{position:fixed;z-index:99999;background:var(--overlay);border:1px solid var(--border);border-radius:8px;padding:6px 10px;font-size:12.5px;color:var(--text-body);max-width:260px;pointer-events:none;opacity:0;transform:translateY(3px);transition:opacity .18s ease,transform .18s ease}
#tt.show{opacity:1;transform:none}
#appinfo{position:fixed;z-index:99998;background:var(--overlay);border:1px solid var(--border);border-radius:10px;padding:12px 14px;font-size:13px;color:var(--text-body);box-shadow:0 8px 24px rgba(0,0,0,.4);display:none;pointer-events:auto;min-width:220px}
#appinfo.show{display:block}
#appinfo a{color:var(--accent)}
main a{color:var(--accent);text-decoration:none;border-bottom:1px solid rgba(0,184,177,.35)}
main a:hover{border-bottom-color:var(--accent)}
#targets{display:flex;gap:8px;margin:0 0 14px;flex-wrap:wrap}
.tpill{display:inline-flex;align-items:center;gap:8px;padding:7px 14px;border-radius:999px;border:1px solid var(--border);background:var(--surface);color:var(--text-body);cursor:pointer;font:500 13.5px 'Satoshi',sans-serif;transition:background .15s ease,border-color .15s ease,color .15s ease,transform .06s ease}
.tpill:hover{border-color:#4b5563;background:var(--elevated);color:var(--text)}
.tpill:active{transform:translateY(1px) scale(.97)}
.tpill.active{border-color:var(--accent);color:var(--text);background:var(--elevated)}
.tdot{width:8px;height:8px;border-radius:999px;background:var(--text-2)}
.tdot.on{background:var(--green)}
.note{font-size:12.5px;color:var(--text-2);margin:-6px 0 12px}
.wins{font:12.5px 'JetBrains Mono',monospace;color:var(--text-2)}
/* ── inline demo recording (John, 2026-09-05) ─────────────────────────── */
.demo-clip{position:relative;margin-top:10px;width:min(100%,360px);border-radius:10px;overflow:hidden;background:#000;border:1px solid #2a3341;}
.demo-clip video{display:block;width:100%;aspect-ratio:16/9;background:#000;}
.demo-clip .clip-play{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:56px;height:56px;border-radius:50%;border:0;background:rgba(45,212,191,.92);color:#04121a;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 18px rgba(0,0,0,.45);}
.demo-clip .clip-play:hover{background:#2dd4bf;transform:translate(-50%,-50%) scale(1.06);}
.demo-clip .clip-cap{position:absolute;left:0;right:0;bottom:0;padding:22px 10px 8px;font-size:12px;color:#e6edf3;background:linear-gradient(transparent,rgba(0,0,0,.75));pointer-events:none;}
.demo-clip.playing .clip-play,.demo-clip.playing .clip-cap{display:none;}
</style>
</head>
<body>
<header>
<div class="brand" id="brand" tabindex="0">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex:none"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M7.5 15.5H9.5L16.5 8.5"/><circle cx="7.5" cy="15.5" r="2.5"/><circle cx="16.5" cy="8.5" r="2.5"/></svg>
KiCad Bridge <span class="sub">· AI control for KiCad</span>
</div>
<div class="vsep"></div>
<!-- dash-header: the standard target chip (which box answers), LED inside;
versions live in the brand popover, NEVER in the bar -->
<span class="target-chip" id="targetChip" data-tip="The box this bridge answers on. Switch boxes with the pills under Status."><span class="tdot" id="chipDot"></span><span id="chipName">connecting...</span></span>
<div class="actions">
<button class="iconbtn" id="btnRefresh" data-tip="Re-read every facet's status from the bridge right now (also auto-refreshes every 15s)" aria-label="Refresh"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6"/></svg></button>
<button class="iconbtn" id="btnHelp" aria-pressed="true" aria-label="Hide the prompts panel" data-tip="Hide the prompts panel"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M9.5 9.5a2.5 2.5 0 1 1 3.5 2.3c-.7.4-1 1-1 1.7"/><path d="M12 17h.01"/></svg></button>
<button class="iconbtn" id="btnSettings" title="Settings" aria-label="Settings"><!-- THE Adom cog: adom-ui-design assets/icons/settings-cog-outline.svg, stroke swapped to currentColor for hover --><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button>
</div>
</header>
<main>
<!-- The panel is MOUNTED, not built: adom-ui-design 2.0.0 ships the one
implementation as a module. Seven apps hand-building from a written spec
produced four paddings, two border colours and three Copy drawings; a
written spec is not a mechanism. We pass phrases, noun and demo only. -->
<div id="newbie"></div>
<script src="newbie-panel.js"></script>
<!-- status-leds: one compact pill row, prose on tap only -->
<div class="leds" id="ledRow" style="margin-top:16px"></div>
<div id="ledDetail" class="led-detail-pop" style="display:none"></div>
<h2>Boxes & actions</h2>
<div id="targets"></div>
<div class="note" id="boxnote" style="display:none">Many boxes, one bridge: pick where KiCad should run. The bridge only ever drives the KiCad instances IT launched, so your own KiCad is never touched.</div>
<div class="btnrow">
<button class="btn primary" id="btnSetup">Setup</button>
<button class="btn" id="btnLaunch">Launch KiCad</button>
<button class="btn" id="btnClose">Close KiCad</button>
</div>
<h2>Demos</h2>
<div class="card">
<div class="led-label">Guided demo</div>
<div class="led-sub">Six beats on a real Adom board from the wiki, the ESC G431 motor controller: its STM32 symbol, that chip's footprint, the chip in 3D, the full schematic, the board in 2D and the board in 3D. Each window is brought to you once, a caption names every beat, and the glass remote at the bottom right is yours: play, pause, next, back, mute, stop.</div>
<div class="bar" id="demoBar"><div></div></div>
<div class="barlabel" id="demoLabel"></div>
<div class="shots" id="demoShots"></div>
</div>
<div class="card" style="margin-top:12px">
<div class="led-label">Tour a wiki part end to end</div>
<div class="led-sub">Pulls a part page from wiki.adom.inc, installs its symbol, footprint and 3D model into KiCad, then generates a single-component project you can open.</div>
<div class="btnrow">
<input id="partPage" value="ray/xl555" data-tip="Any wiki part page that ships .kicad_sym / .kicad_mod / .step files" style="flex:1;min-width:180px;background:var(--elevated);border:1px solid var(--border);border-radius:8px;color:var(--text);padding:8px 10px;font:13px 'JetBrains Mono',monospace">
<button class="btn" id="btnPartTour">Tour this part</button>
</div>
<div class="barlabel" id="partLabel"></div>
</div>
<h2>KiCad surfaces</h2>
<div class="note" id="surfaceIntro">A real wiki part, end to end. Each button fetches the part's files from its wiki page, installs them into your real KiCad, opens JUST that surface in the background, and renders it from kicad_state. This is the whole circle: a wiki component to the app the EE designs in.</div>
<div class="grid" id="surfaceGrid"></div>
<h2 style="display:flex;align-items:center;gap:10px">Verb times on this box
<button class="btn" id="btnVerbTimes" style="font-size:12.5px" data-tip="Measured run times per verb from this box's append-only run log. p90 is the column for hunting slow paths.">refresh</button></h2>
<div class="note">Every dispatch is logged with its outcome; these numbers converge just by the bridge being used. p50 15s beside p90 400s is two code paths wearing one name.</div>
<div class="card" id="verbTimes" style="overflow-x:auto"><div class="note">not loaded - press refresh</div></div>
<h2>Copy-paste prompts for your AI</h2>
<div id="prompts"></div>
</main>
<dialog id="dlgSettings" class="settings" tabindex="-1" aria-labelledby="settingsTitle">
<div class="settings-head" id="settingsHead">
<h2 id="settingsTitle">KiCad Bridge Settings <span class="card-status" id="settingsBox"></span></h2>
<button class="iconbtn" id="btnCloseSettings" aria-label="Close settings" title="Close"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg></button>
</div>
<div class="settings-body" id="settingsBody">
<section class="settings-card" id="card-activity">
<h3 class="card-title">Taskbar activity <span class="card-status" id="stActivity"></span></h3>
<p class="card-desc">While a verb is driving KiCad, the bridge marks the KiCad taskbar button so you can tell the window is busy and who is busy in it. The progress bar is painted through the Windows taskbar API (<code>ITaskbarList3</code>) and sized from this box's own measured verb timings: it fills over the time that verb usually takes here, creeps past the end when a run is overdue, and shows an indeterminate marquee when nothing has been measured yet. The flash is Windows' own attention highlight. The bridge never triggers it, it only decides whether to cancel it while a bar is showing.</p>
<p class="card-desc">The difference is what each one asks of you. A flash stays lit until you click the button, so it reads as something you have to deal with. A bar removes itself the moment Adom stops driving, so when the bar and the overlay badge are both gone, the window is yours again. Read-only verbs such as status checks never mark activity in any mode.</p>
<div class="wrapper">
<h4 class="name" title="activityIndicator"><span class="prefix">Taskbar: </span><span class="title">Activity indicator</span></h4>
<fieldset class="choice-set" role="radiogroup" aria-labelledby="activityLabel">
<legend id="activityLabel" class="set-check-label" style="padding:0;margin-bottom:.35rem">What the KiCad taskbar button does while Adom is driving it. Each picture is a 1:1 capture of this machine's taskbar at its native resolution.</legend>
<label class="choice" data-mode="progress" data-selected="false">
<input type="radio" name="activityIndicator" value="progress">
<img src="assets/shots/taskbar-progress.png" width="272" height="88" loading="lazy" alt="The KiCad taskbar button with a blue progress bar about two thirds full under its icon, neighbours untouched">
<span class="choice-name">Progress bar</span>
<span class="choice-desc">Fills over the verb's usual duration on this box, then clears itself. Cancels the OS flash.</span>
</label>
<label class="choice" data-mode="flash" data-selected="false">
<input type="radio" name="activityIndicator" value="flash">
<img src="assets/shots/taskbar-flash.png" width="272" height="88" loading="lazy" alt="The same KiCad taskbar button tinted salmon with a highlighted underline, no progress bar">
<span class="choice-name">Attention flash</span>
<span class="choice-desc">Windows' own highlight, left alone. Says something happened, not how long is left, and stays until you click it.</span>
</label>
<label class="choice" data-mode="both" data-selected="false">
<input type="radio" name="activityIndicator" value="both">
<img src="assets/shots/taskbar-both.png" width="272" height="88" loading="lazy" alt="The same KiCad taskbar button tinted salmon and carrying the blue progress bar at the same time">
<span class="choice-name">Bar and flash</span>
<span class="choice-desc">Both at once. The bar still clears itself; the tint stays until you click.</span>
</label>
<label class="choice" data-mode="none" data-selected="false">
<input type="radio" name="activityIndicator" value="none">
<img src="assets/shots/taskbar-none.png" width="272" height="88" loading="lazy" alt="The same KiCad taskbar button with no bar and no highlight, only the running dot">
<span class="choice-name">Nothing</span>
<span class="choice-desc">The button never changes. You rely on the overlay badge below, or on nothing.</span>
</label>
</fieldset>
<span class="default-hint">Default: Progress bar</span>
</div>
</section>
<section class="settings-card" id="card-guard">
<h3 class="card-title">Keep KiCad in the background <span class="card-status" id="stGuard"></span></h3>
<p class="card-desc">When Adom opens a KiCad window, KiCad tries to bring it to the front of whatever you are doing. With this on, the bridge sends the window it is opening right now behind your window. It only ever touches the window being opened at that moment: a KiCad window that is already open is yours to raise by clicking its taskbar button, Alt-Tab or anything else. With this off, the bridge never moves or pushes any KiCad window, so you see exactly what KiCad does by itself. Off by default while it is still being tuned: with it on, KiCad can behave oddly (wiki #114). Flip it to compare the two; the change applies at once and the bridge's focus log keeps recording what it would have done.</p>
<div class="wrapper">
<h4 class="name" title="backgroundGuard"><span class="prefix">Windows: </span><span class="title">Send new KiCad windows behind mine</span></h4>
<div class="setting-row">
<button class="toggle" id="tglGuard" role="switch" aria-checked="true" aria-label="Send a KiCad window Adom is opening behind my window"><span class="toggle-track"><span class="toggle-thumb"></span></span></button>
<span class="set-check-label">Send a KiCad window Adom is opening behind my window</span>
</div>
<span class="default-hint">Default: Off</span>
</div>
</section>
<section class="settings-card" id="card-overlay">
<h3 class="card-title">Overlay badge <span class="card-status" id="stOverlay"></span></h3>
<p class="card-desc">The Adom mark is stamped in the corner of a KiCad window's taskbar button while a verb is running against that window, and comes off by itself five minutes after the last verb, so a badge always means "Adom is driving this right now". It is a per-window overlay through the same taskbar API, so it never changes the button's own icon or how Windows groups it, and it is never stamped by read-only verbs like status checks. A bridge restart clears every badge, because a freshly started bridge is driving nothing.</p>
<div class="shot-pair">
<figure class="setting-shot">
<img src="assets/shots/taskbar-overlay.png" width="272" height="88" loading="lazy" alt="The KiCad taskbar button with the small teal Adom mark in its top right corner">
<figcaption><b>On:</b> the Adom mark sits on the KiCad PCB Editor button while a verb drives it.</figcaption>
</figure>
<figure class="setting-shot">
<img src="assets/shots/taskbar-none.png" width="272" height="88" loading="lazy" alt="The same KiCad taskbar button with no mark on it, only its own icon">
<figcaption><b>Off:</b> the same button, same moment, nothing added.</figcaption>
</figure>
</div>
<div class="wrapper">
<h4 class="name" title="overlayBadges"><span class="prefix">Taskbar: </span><span class="title">Adom overlay badge</span></h4>
<div class="setting-row">
<button class="toggle" id="tglOverlay" role="switch" aria-checked="true" aria-label="Show the Adom overlay badge while driving"><span class="toggle-track"><span class="toggle-thumb"></span></span></button>
<span class="set-check-label">Show the badge while Adom is driving</span>
</div>
<span class="default-hint">Default: On</span>
</div>
</section>
<footer class="settings-footer">
<p class="footer-note">Settings are stored on the selected box in <code>%USERPROFILE%\.adom\kicad-bridge-settings.json</code> and apply immediately. Restoring returns every option above to its default.</p>
<button class="restore-btn" id="btnRestore" type="button"><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path id="restoreIcon" fill="currentColor" d="M13 3a9 9 0 0 0-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42A8.954 8.954 0 0 0 13 21a9 9 0 0 0 0-18m-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8z"/></svg><span id="restoreLabel">Restore defaults</span></button>
</footer>
</div>
</dialog>
<dialog id="dlgSetup">
<h2 style="margin:0 0 8px">Setup</h2>
<div id="setupBody" class="led-sub" style="font-size:14px"></div>
<div class="btnrow" style="justify-content:flex-end">
<button class="btn primary" id="btnSetupNext">Run the next step</button>
<button class="btn" id="btnCloseSetup">Close</button>
</div>
</dialog>
<!-- dash-demo player: a remote control, fixed bottom-center while the demo runs -->
<div id="demoToolbar" style="display:none">
<button class="dt-btn" id="dtPrev" data-tip="Previous step" aria-label="Previous step">⏮</button>
<button class="dt-btn" id="dtPlay" data-tip="Pause or resume" aria-label="Pause or resume">⏸</button>
<button class="dt-btn" id="dtNext" data-tip="Next step" aria-label="Next step">⏭</button>
<span class="dt-sep"></span>
<button class="dt-btn" id="dtMute" data-tip="Mute or unmute the narration" aria-label="Mute or unmute">🔊</button>
<span class="dt-step" id="dtStep">step 1/6</span>
<button class="dt-btn" id="dtExit" data-tip="Stop the demo, clear the caption, silence narration" aria-label="Exit the demo">✕</button>
</div>
<div id="toasts"></div>
<div id="tt" role="tooltip"></div>
<div id="appinfo"></div>
<script>
const $=s=>document.querySelector(s);
const consoleBuf=[];
["log","warn","error"].forEach(k=>{const o=console[k].bind(console);console[k]=(...a)=>{consoleBuf.push(k+": "+a.join(" "));if(consoleBuf.length>300)consoleBuf.shift();o(...a);};});
window.onerror=(m)=>{consoleBuf.push("onerror: "+m);};
function toast(msg,kind){const t=document.createElement('div');t.className='toast '+(kind||'');
const ICONS={ok:'M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z',err:'M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8 8 8 0 0 1 8-8 8 8 0 0 1 8 8 8 8 0 0 1-8 8'};
if(ICONS[kind]){t.innerHTML='<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" style="flex:none;color:var(--'+(kind==='ok'?'green':'red')+')"><path fill="currentColor" d="'+ICONS[kind]+'"/></svg><span></span>';t.lastChild.textContent=msg;t.style.display='flex';t.style.alignItems='center';t.style.gap='8px';}
else t.textContent=msg;
$('#toasts').appendChild(t);setTimeout(()=>t.remove(),kind==='err'?9000:4000);}
async function api(path,opts){const r=await fetch(path,opts);return r.json();}
async function act(action,args){return api('api/action',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action,target:curTarget||undefined,args:args||{}})});}
let lastStatus=null;
let curTarget=localStorage.getItem('kdTarget')||'';
let targetsCache=[];
// The adom.cloud proxy STRIPS query strings on sub-paths, so target rides the PATH.
function statusUrl(force){return 'api/status/'+encodeURIComponent(curTarget||'-')+(force?'/force':'');}
async function loadTargets(){
try{
const d=await api('api/targets');
targetsCache=d.targets||[];
const el=$('#targets');el.innerHTML='';
$('#boxnote').style.display=targetsCache.length>1?'':'none';
const known=targetsCache.map(t=>t.name);
if(targetsCache.length&&(!curTarget||!known.includes(curTarget))){
if(curTarget)toast(curTarget+' is no longer connected - switching to '+known[0]);
curTarget=(targetsCache.find(t=>t.kicadRunning)||targetsCache[0]).name;
localStorage.setItem('kdTarget',curTarget);
}
for(const t of targetsCache){
const b=document.createElement('button');b.className='tpill'+(t.name===curTarget?' active':'');
const resp=(t.responding===false)?'not responding':(t.kicadRunning?'KiCad is running here':'KiCad is closed here');
b.setAttribute('data-tip',resp+(t.kicadVersion?(' - KiCad '+t.kicadVersion):''));
if(t.responding===false)b.style.opacity='.55';
b.innerHTML='<span class="tdot'+(t.kicadRunning?' on':'')+'"></span>'+t.name;
b.onclick=()=>{curTarget=t.name;localStorage.setItem('kdTarget',curTarget);loadTargets();refresh();toast('switched to '+t.name)};
el.appendChild(b);
}
}catch(e){}
}
// status-leds (1.9.1): one compact pill row; prose lives in tap-to-expand.
// A busy pill that stays busy past 15 minutes escalates to "stuck?" - a
// busy-for-a-day is broken, not busy (the fusion add-in lesson).
const BUSY_KEY='kdBusySince';
function busySince(){try{return JSON.parse(localStorage.getItem(BUSY_KEY))||{}}catch(e){return{}}}
function busySave(o){try{localStorage.setItem(BUSY_KEY,JSON.stringify(o))}catch(e){}}
function shortState(l){
// The SERVER emits `short` (#59). The page NEVER truncates prose - that is
// what produced the fleet's "in talled and pinned".
return l.short || l.state || 'unknown';
}
function renderLeds(st){
const g=$('#ledRow');g.innerHTML='';
const leds=st.leds||{};
const ORDER=['relay','box','bridge','kicad','update','plugin','focus','presence','suite'];
const keys=ORDER.filter(k=>leds[k]).concat(Object.keys(leds).filter(k=>!ORDER.includes(k)));
const bs=busySince();const now=Date.now();let bsChanged=false;
for(const key of keys){
const l=leds[key];
let state=l.state==='ok'?'ok':(l.state==='busy'?'busy':(l.state==='err'?'bad':(l.state==='warn'?'busy':'dim')));
if(state==='busy'){
if(!bs[key]){bs[key]=now;bsChanged=true;}
else if(now-bs[key]>15*60*1000)state='stuck';
}else if(bs[key]){delete bs[key];bsChanged=true;}
const el=document.createElement('button');el.className='led-pill';el.dataset.state=state;el.dataset.key=key;
el.innerHTML=`<span class="dot"></span><b>${l.label||key}</b><span class="st">${state==='stuck'?'stuck?':shortState(l)}</span>`;
el.onclick=(ev)=>{ev.stopPropagation();showLedDetail(el,key,l,state,st);};
g.appendChild(el);
}
if(bsChanged)busySave(bs);
const chip=$('#chipName');if(chip){chip.textContent=st.target?('target: '+st.target):'no bridge target';}
const dot=$('#chipDot');if(dot){const ok=(leds.relay||{}).state==='ok'&&(leds.box||{}).state==='ok';dot.className='tdot'+(ok?' on':'');}
}
// tap-to-expand detail: prose, timestamps, and for a red pill the NEXT STEP
// first (the UI cousin of the CLI Hint: contract). The Plugin pill also
// carries the open-KiCad-windows list (its old home was a raw mono dump).
function showLedDetail(anchor,key,l,state,st){
const d=$('#ledDetail');
let h='<h4>'+(l.label||key)+' · '+(state==='stuck'?'stuck?':(l.state||''))+'</h4>';
if(state==='bad'&&l._hint)h+='<div><b>Next step:</b> '+l._hint+'</div>';
if(state==='stuck')h+='<div><b>Next step:</b> this has been busy over 15 minutes, which usually means wedged. Press Refresh; if it stays stuck, relaunch from the dock (the version-aware serve replaces a stale instance).</div>';
if(l.detail)h+='<div style="margin-top:6px">'+l.detail+'</div>';
if(l._hint&&state!=='bad')h+='<div class="hint">'+l._hint+'</div>';
if(key==='plugin'||key==='kicad'){
const w=(st&&st.windows)||[];
h+='<div class="winlist">'+(w.length?('open KiCad windows:\n'+w.join('\n')):'no KiCad windows open')+'</div>';
}
d.innerHTML=h;d.style.display='block';
const r=anchor.getBoundingClientRect(),M=8;
let x=Math.max(M,Math.min(r.left,innerWidth-d.offsetWidth-M));
let y=r.bottom+8;if(y+d.offsetHeight>innerHeight-M)y=r.top-d.offsetHeight-8;
d.style.left=x+'px';d.style.top=Math.max(M,y)+'px';
}
document.addEventListener('click',()=>{const d=$('#ledDetail');if(d)d.style.display='none';});
const LED_SKELETON=['relay','box','bridge','kicad','update','plugin','focus','presence'];
function renderSkeleton(){
const g=$('#ledRow');if(g.children.length)return; // only before the first real paint
g.innerHTML=LED_SKELETON.map(k=>`<span class="led-pill" data-state="dim"><span class="dot"></span><b style="text-transform:capitalize">${k}</b><span class="st">checking...</span></span>`).join('');
const chip=$('#chipName');if(chip)chip.textContent='checking '+(curTarget||'the default box')+'...';
}
let refreshing=false;
async function refresh(force){
if(refreshing)return;refreshing=true;renderSkeleton();
try{lastStatus=await api(statusUrl(force));renderLeds(lastStatus);maybeCountLaunch(lastStatus);}
catch(e){
const chip=$('#chipName');if(chip)chip.textContent='status unavailable';
$('#ledRow').innerHTML='<button class="led-pill" data-state="bad"><span class="dot"></span><b>Status</b><span class="st">unavailable</span></button>';
toast('status failed: '+e+' - pick another box with the pills below, or press Refresh','err');
}
finally{refreshing=false;}
}
$('#btnRefresh').onclick=()=>{toast('refreshing status');refresh(true);};
$('#btnLaunch').onclick=async()=>{toast('launching KiCad in the background - watch the KiCad LED');await act('launch');};
$('#btnClose').onclick=async()=>{toast('closing the bridge\u2019s KiCad windows');let r=await act('close');if(r&&r.errorCode==='unsaved_changes'){const u=(r.unsavedChanges||[]).map(x=>x.window).join(', ');if(confirm('KiCad has unsaved changes in: '+u+'\n\nDiscard them and close?')){r=await act('close',{discard:true});}else{toast('left open: save in KiCad, then close again','warn');}}setTimeout(()=>refresh(true),4000);};
// Phrases come from dockbar.json `say` via the server, then the MODULE owns
// everything else: card, grid, table, icons, clipboard, send, toasts, X and
// the header toggle. We only tell it our noun and what the demo does.
function buildSay(){
// Phrases are injected into the document at serve time (window.__SAY_PHRASES__),
// so there is no fetch to race at boot. The module may still be a moment
// behind if its <script> is slow, so poll briefly for it rather than
// returning and leaving the page with no panel.
const phrases=(window.__SAY_PHRASES__||[]).slice(0,6);
if(!phrases.length)return;
const attempt=()=>{
if(!window.AdomNewbiePanel)return false;
try{ doMount(phrases); }catch(e){ console.error('newbie panel mount failed',e); }
return true;
};
if(attempt())return;
let tries=0;
const t=setInterval(()=>{ if(attempt()||++tries>20)clearInterval(t); },250);
}
function doMount(phrases){
AdomNewbiePanel.mount({
mount:'#newbie', app:'kicad', appName:'KiCad Bridge', slug:'kicad-bridge',
toggleButton:'#btnHelp',
demo:{label:'Play the demo', meta:'6 views, about 2 minutes',
onPlay:function(){ startDemo(); }},
phrases:phrases.slice(0,6)
});
mountVideo();
}
// ── the recorded tour, inline in the newbie panel (John, 2026-09-05) ───────
// A small thumbnail with a play button under the demo button; it plays right
// there, and the player's own fullscreen control hands off to Hydrogen, whose
// WebView2 grows the webview to 90% of the window on the standard Fullscreen API.
const DEMO_VIDEO_URL='https://wiki.adom.inc/api/pages/adom/kicad-bridge/files/docs/videos/kicad-demo-tour.mp4?v=20260905c'; // bump the query when the cut changes: same path, fresh fetch
// poster inlined: the wiki serves repo files as application/octet-stream with nosniff, which Chromium will not render as an image
const DEMO_VIDEO_POSTER='data:image/jpeg;base64,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';
function mountVideo(){
const demoBtn=document.querySelector('#newbie .anp-demo'); if(!demoBtn||document.getElementById('demoClip'))return;
const col=demoBtn.parentElement;
const wrap=document.createElement('div'); wrap.id='demoClip'; wrap.className='demo-clip';
wrap.innerHTML='<video id="demoVideo" poster="'+DEMO_VIDEO_POSTER+'" preload="none" playsinline controlslist="nodownload"></video>'+
'<button type="button" class="clip-play" aria-label="Play the recording"><svg viewBox="0 0 24 24" width="28" height="28" fill="currentColor"><path d="M8 5v14l11-7z"/></svg></button>'+
'<div class="clip-cap">The demo, recorded (2:51)</div>';
col.appendChild(wrap);
const v=wrap.querySelector('video'), play=wrap.querySelector('.clip-play');
play.addEventListener('click',()=>{ if(!v.src){ v.src=DEMO_VIDEO_URL; } v.controls=true; wrap.classList.add('playing'); v.play().catch(()=>{}); }); // native controls (with fullscreen) appear once it plays
v.addEventListener('pause',()=>{ if(!document.fullscreenElement) wrap.classList.remove('playing'); });
v.addEventListener('play',()=>wrap.classList.add('playing'));
v.addEventListener('ended',()=>{ wrap.classList.remove('playing'); });
}
// Guided demo with a REAL progress bar + the stepShot stream (one screenshot per finished beat).
async function startDemo(){
toast(demoMuted()?'starting the guided demo MUTED (narration off): click the speaker on the pill or the remote to unmute':'starting the guided demo');
$('#demoShots').innerHTML='';
const s=await act('demo',{muted:demoMuted()});const d=s.data||s;
if(d.success===false){toast('demo failed to start: '+(d.error||'unknown'),'err');return;}
$('#demoBar').style.display='block';
$('#demoToolbar').style.display='flex';document.body.classList.add('demo-running');
const poll=setInterval(async()=>{
const p=await act('demo_progress');const q=p.data||p;
const paused=(q.stepLabel==='Paused');
$('#dtPlay').dataset.paused=paused?'1':'0';
$('#dtPlay').innerHTML=paused?'▶':'⏸';
if(q.beatIndex&&q.beatCount)$('#dtStep').textContent='step '+q.beatIndex+'/'+q.beatCount;
$('#demoBar>div').style.width=(q.percent||0)+'%';
$('#demoLabel').textContent=(q.percent||0)+'% '+(q.stepLabel||'');
if(q.stepShot&&q.stepShot.image){
const c=document.createElement('div');c.className='shot';
c.innerHTML='<img src="data:image/png;base64,'+q.stepShot.image+'" alt="'+(q.stepShot.title||'')+'"><div class="cap">'+(q.stepShot.title||q.stepShot.step||'')+'</div>';
$('#demoShots').appendChild(c);
}
// Mirror the desktop remote: mute face, and a STOP (the remote's X) hides
// this pill at once with a toast, before the bridge finishes the beat in flight.
if(typeof q.muted==='boolean'){$('#dtMute').dataset.muted=q.muted?'1':'0';$('#dtMute').innerHTML=q.muted?'🔇':'🔊';}
if(q.stopping&&!$('#demoToolbar').dataset.closing){$('#demoToolbar').dataset.closing='1';$('#demoToolbar').style.display='none';document.body.classList.remove('demo-running');toast('closing the demo tour','ok');}
if(q.done){clearInterval(poll);$('#demoToolbar').style.display='none';document.body.classList.remove('demo-running');delete $('#demoToolbar').dataset.closing;toast(q.error?('demo failed: '+q.error):(q.result&&q.result.exited?'demo stopped':'demo complete'),'' + (q.error?'err':'ok'));refresh(true);}
},3000);
}
// Demo transport: mirrors the ab toast buttons; the bridge applies commands
// between beats (a beat that is mid-open finishes first).
async function demoCtl(cmd){
const r=await act('demo_control',{cmd});const d=(r&&r.data)||r||{};
if(d.error){toast('demo control failed: '+d.error,'err');return;}
if(cmd==='pause'){$('#dtPlay').dataset.paused='1';$('#dtPlay').innerHTML='▶';toast('pausing after this step','ok');}
if(cmd==='play'){$('#dtPlay').dataset.paused='0';$('#dtPlay').innerHTML='⏸';toast('resuming','ok');}
if(cmd==='exit')toast('stopping the demo','ok');
if(cmd==='next')toast('skipping ahead','ok');
if(cmd==='prev')toast('replaying the previous step','ok');
}
$('#btnPartTour').onclick=async()=>{
const page=$('#partPage').value.trim();
const b=$('#btnPartTour');b.disabled=true;
toast('touring '+page+' (clone, install, generate project)');
$('#partLabel').textContent='working...';
const r=await act('part_tour',{page});const d=r.data||r;
b.disabled=false;
if(d.success===false){toast('part tour failed: '+(d.error||'unknown'),'err');$('#partLabel').textContent=d.error||'failed';}
else{toast('part installed and project generated','ok');$('#partLabel').textContent=(d.steps||[]).join(' · ');refresh(true);}
};
// "Say it": phrases come from dockbar.json `say` via the server, so the dock
// interstitial and this strip teach the same words. Copy on click; Try it
// stages the phrase into the app's AI thread when the Hydrogen control API is
// reachable, and copies with an honest toast when it is not.
// ---- Settings (settings-layout skill: modal, four dismissals, optimistic saves) ----
// The taskbar captures are native DEVICE pixels (2560-wide display at 150%).
// Show them at naturalWidth / devicePixelRatio so they render 1:1 - the same
// physical size as the taskbar they were cut from - instead of being
// upscaled by the browser on a HiDPI screen (caught by John: "grainy").
function sizeShotsToDevicePixels(){
const dpr=window.devicePixelRatio||1;
document.querySelectorAll('#dlgSettings img[width]').forEach(img=>{
const w=parseInt(img.getAttribute('width'),10);if(!w)return;
img.style.width=(w/dpr)+'px';img.style.height='auto';img.style.maxWidth='100%';
});
}
sizeShotsToDevicePixels();window.addEventListener('resize',sizeShotsToDevicePixels);
const SETTINGS_DEFAULTS={activityIndicator:'progress',overlayBadges:true,backgroundGuard:false};
const MODE_LABEL={progress:'Progress bar',flash:'Attention flash',both:'Bar and flash',none:'Nothing'};
const dlg=$('#dlgSettings');
// Local mirror: the box's settings file is the truth, but the page reflects a
// click before the round trip and survives a stalled one (skill: local-first).
let settingsView=Object.assign({},SETTINGS_DEFAULTS,JSON.parse(localStorage.getItem('kdSettings')||'{}'));
function rememberSettings(){localStorage.setItem('kdSettings',JSON.stringify(settingsView));}
function renderSettings(){
const mode=MODE_LABEL[settingsView.activityIndicator]?settingsView.activityIndicator:'progress';
document.querySelectorAll('#dlgSettings .choice').forEach(c=>{
const sel=c.dataset.mode===mode;c.dataset.selected=String(sel);c.querySelector('input').checked=sel;});
$('#stActivity').textContent=MODE_LABEL[mode];$('#stActivity').className='card-status'+(mode!=='none'?' ok':'');
const on=settingsView.overlayBadges!==false;
const t=$('#tglOverlay');t.classList.toggle('active',on);t.setAttribute('aria-checked',String(on));
$('#stOverlay').textContent=on?'on':'off';$('#stOverlay').className='card-status'+(on?' ok':'');
const gOn=settingsView.backgroundGuard===true;
const g=$('#tglGuard');g.classList.toggle('active',gOn);g.setAttribute('aria-checked',String(gOn));
$('#stGuard').textContent=gOn?'on':'off';$('#stGuard').className='card-status'+(gOn?' ok':'');
$('#settingsBox').textContent=curTarget?('on '+curTarget):'';
}
// Bounded, optimistic save: the DOM already shows the click; a failed or
// stalled round trip toasts and reverts to what the box last reported.
async function saveSetting(action,args,revert){
const ctrl=new AbortController();const timer=setTimeout(()=>ctrl.abort(),5000);
try{
const r=await fetch('api/action',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({action,target:curTarget||undefined,args}),signal:ctrl.signal}).then(x=>x.json());
const d=(r&&r.data)||r||{};
if(d.error||d.success===false)throw new Error(d.error||'the bridge refused the change');
if(d.settings){Object.assign(settingsView,d.settings);rememberSettings();renderSettings();}
return true;
}catch(e){
toast('setting not saved: '+(e.name==='AbortError'?'the box did not answer within 5s':e.message),'err');
if(revert){Object.assign(settingsView,revert);rememberSettings();renderSettings();}
return false;
}finally{clearTimeout(timer);}
}
function openSettings(){
if(lastStatus&&lastStatus.settings&&Object.keys(lastStatus.settings).length){Object.assign(settingsView,lastStatus.settings);rememberSettings();}
renderSettings();
dlg.style.margin='';dlg.style.left='';dlg.style.top='';
dlg.showModal();
// Open at the TOP: showModal autofocuses the first focusable child, which
// scrolls to it. Focus the dialog itself and reset scroll, again next frame.
dlg.focus();const body=$('#settingsBody');body.scrollTop=0;requestAnimationFrame(()=>{body.scrollTop=0;});
}
$('#btnSettings').onclick=openSettings;
$('#btnCloseSettings').onclick=()=>dlg.close();
// Click OUTSIDE closes: the backdrop is part of the dialog element, so gate on
// the pointer being outside the dialog's own box, or padding clicks close it.
dlg.addEventListener('click',e=>{
if(e.target!==dlg)return;
const r=dlg.getBoundingClientRect();
const inside=e.clientX>=r.left&&e.clientX<=r.right&&e.clientY>=r.top&&e.clientY<=r.bottom;
if(!inside)dlg.close();
});
// Draggable by its header, clamped to the viewport.
(()=>{const head=$('#settingsHead');let drag=null;
head.addEventListener('pointerdown',e=>{if(e.target.closest('button'))return;
const r=dlg.getBoundingClientRect();drag={dx:e.clientX-r.left,dy:e.clientY-r.top};head.setPointerCapture(e.pointerId);});
head.addEventListener('pointermove',e=>{if(!drag)return;
const r=dlg.getBoundingClientRect();
const x=Math.max(0,Math.min(e.clientX-drag.dx,innerWidth-r.width));
const y=Math.max(0,Math.min(e.clientY-drag.dy,innerHeight-r.height));
dlg.style.margin='0';dlg.style.left=x+'px';dlg.style.top=y+'px';});
head.addEventListener('pointerup',()=>{drag=null;});head.addEventListener('pointercancel',()=>{drag=null;});
})();
document.querySelectorAll('#dlgSettings .choice input').forEach(inp=>{inp.addEventListener('change',()=>{
const prev={activityIndicator:settingsView.activityIndicator};
settingsView.activityIndicator=inp.value;rememberSettings();renderSettings();
saveSetting('set_activity_indicator',{mode:inp.value},prev).then(ok=>{if(ok)toast('taskbar activity: '+MODE_LABEL[inp.value],'ok');});
});});
$('#tglOverlay').onclick=()=>{
const prev={overlayBadges:settingsView.overlayBadges!==false};
const now=!prev.overlayBadges;
settingsView.overlayBadges=now;rememberSettings();renderSettings();
saveSetting('set_overlay_badges',{overlayBadges:now},prev).then(ok=>{if(ok)toast('overlay badge '+(now?'on':'off'),'ok');});
};
$('#tglGuard').onclick=()=>{
const prev={backgroundGuard:settingsView.backgroundGuard===true};
const now=!prev.backgroundGuard;
settingsView.backgroundGuard=now;rememberSettings();renderSettings();
saveSetting('set_background_guard',{backgroundGuard:now},prev).then(ok=>{if(ok)toast('keep KiCad in the background: '+(now?'on':'off'),'ok');});
};
// Two-click Restore defaults, auto-disarming after 4s. The button IS the confirmation.
(()=>{const b=$('#btnRestore');let armed=false,timer=null;
const RESTORE='M13 3a9 9 0 0 0-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42A8.954 8.954 0 0 0 13 21a9 9 0 0 0 0-18m-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8z';
const ALERT='M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8';
function disarm(){armed=false;b.classList.remove('armed');$('#restoreLabel').textContent='Restore defaults';$('#restoreIcon').setAttribute('d',RESTORE);clearTimeout(timer);}
b.onclick=()=>{
if(!armed){armed=true;b.classList.add('armed');$('#restoreLabel').textContent='Click again to confirm';$('#restoreIcon').setAttribute('d',ALERT);timer=setTimeout(disarm,4000);return;}
disarm();
const prev=Object.assign({},settingsView);
Object.assign(settingsView,SETTINGS_DEFAULTS);rememberSettings();renderSettings();
saveSetting('restore_settings',{},prev).then(ok=>{if(ok)toast('settings restored to defaults','ok');});
};
})();
// Setup: walk the not-green facets in order, one action per click.
const SETUP_ORDER=[
{led:'kicad',action:'upgrade',label:'Install KiCad (silent, per-user, no UAC)'},
{led:'update',action:'upgrade',label:'Update KiCad to the latest release'},
{led:'plugin',action:'launch',label:'Launch KiCad so the reverse-bridge plugin binds'},
];
function setupPending(){if(!lastStatus)return[];return SETUP_ORDER.filter(s=>{const l=lastStatus.leds[s.led];return l&&l.state!=='ok'&&l.state!=='off';});}
function renderSetup(){
const p=setupPending();
$('#setupBody').innerHTML=p.length?('Remaining steps:<br>'+p.map((s,i)=>`${i+1}. ${s.label}`).join('<br>')):'Everything is configured. You are ready to go.';
$('#btnSetupNext').style.display=p.length?'':'none';
}
$('#btnSetup').onclick=async()=>{await refresh(true);renderSetup();$('#dlgSetup').showModal();};
$('#btnCloseSetup').onclick=()=>$('#dlgSetup').close();
$('#btnSetupNext').onclick=async()=>{
const p=setupPending();if(!p.length)return;
toast('running: '+p[0].label);
await act(p[0].action);
setTimeout(async()=>{await refresh(true);renderSetup();},5000);
};
// Copy-paste AI prompts (teach the user to drive the bridge through their AI).
const PROMPTS=[
"run the kicad demo and show me each step screenshot as it arrives",
"tour the wiki part ray/xl555 in kicad: symbol, footprint, 3d chip, project, schematic, 2d board, 3d board",
"install the Adom Basic Parts and Adom RP2040 libraries from adom/kicad-pcm into kicad",
"open adom/drone-motor-carrier in kicad and show me the schematic, the 2d board and the 3d board",
"check adom/esc-g431 against the 3rd party fab 2-layer design rules in kicad and list every violation",
"export gerbers for adom/carrier-fc-rp2350a-mini from kicad and tell me the KiCad version stamp",
"are any kicad windows in my foreground? prove the bridge stayed in the background"
];
$('#prompts').innerHTML=PROMPTS.map(p=>`<div class="prompt"><code>${p}</code><button class="btn copy" onclick="navigator.clipboard.writeText('${p.replace(/'/g,"\\'")}');toast('copied','ok')">Copy</button></div>`).join('');
// KiCad surfaces: ab's per-part ladder, driven through /api/action {surface}.
// Each card owns its own log line, progress bar and rendered screenshot; the
// server streams `surface` SSE events as it stages, opens and renders.
const SURF_CARDS={};
async function buildSurfaces(){
const r=await act('surfaces');const d=r.data||r;
const g=$('#surfaceGrid');g.innerHTML='';
const part=d.part||{};
$('#surfaceIntro').innerHTML="A real wiki part, end to end: <a href='https://wiki.adom.inc/"+
(part.page||'')+"' target='_blank' rel='noopener'>"+(part.name||'the part')+"</a>. "+
"Each button fetches the part's files from its wiki page, installs them into your real KiCad, "+
"opens JUST that surface in the background, and renders it from kicad_state. This is the whole "+
"circle: a wiki component to the app the EE designs in.";
const rows=(d.surfaces||[]).concat([{key:'state',label:'Show KiCad state',note:'captures every KiCad window as a screenshot'}]);
for(const s of rows){
const el=document.createElement('div');el.className='card scard';
el.innerHTML=`<button class="btn" data-tip="${s.note}">${s.label}${s.key!=='state'?' ('+(part.name||'')+')':''}</button>`+
`<div class="sbar"><div></div></div><div class="slog">not run yet</div>`+
`<div class="sshot"><img alt="${s.label}"></div>`;
el.querySelector('button').onclick=()=>{
el.classList.remove('err');
el.querySelector('.slog').textContent='starting...';
el.querySelector('.sbar').style.display='block';
el.querySelector('.sbar>div').style.width='5%';
act('surface',{surface:s.key});
};
SURF_CARDS[s.key]=el;g.appendChild(el);
}
}
function onSurfaceEvent(e){
const el=SURF_CARDS[e.surface];if(!el)return;
const bar=el.querySelector('.sbar');
if(e.live){ // a real measured frame from the bridge (kicad_progress)
if(e.caller&&!String(e.caller).startsWith('kicad-dashboard')&&!gradState().aiDriven){gradSave({aiDriven:true});}
bar.style.display='block';
bar.classList.toggle('indet', e.percent==null); // unknown -> marquee, never 0%
bar.classList.toggle('dim', e.confidence!=='measured'); // a guess never looks measured
bar.classList.toggle('amber', !!(e.overdue||e.blockedBy));
if(e.percent!=null)bar.querySelector('div').style.width=e.percent+'%';
let lbl=e.stepLabel||'';
if(e.blockedBy)lbl='waiting on you: '+e.blockedBy;
else if(e.etaSec!=null&&!e.overdue)lbl+=' · ~'+Math.round(e.etaSec)+'s left';
else if(e.overdue)lbl+=' · taking longer than usual';
if(lbl)el.querySelector('.slog').textContent=lbl;
return;
}
bar.classList.remove('indet','dim','amber');
if(e.log)el.querySelector('.slog').textContent=e.log;
if(typeof e.percent==='number'){bar.style.display='block';bar.querySelector('div').style.width=e.percent+'%';}
if(e.status==='error'){el.classList.add('err');el.querySelector('.sbar').style.display='none';toast(e.surface+': '+e.log,'err');}
if(e.status==='done'){
el.classList.remove('err');
setTimeout(()=>{el.querySelector('.sbar').style.display='none';},800);
if(e.image){const box=el.querySelector('.sshot');box.style.display='block';
box.querySelector('img').src='data:image/png;base64,'+e.image;}
toast(e.log,'ok');
}
}
// Live channel (SSE): state pushes, AI toasts, CLI click directives.
const CLICK_TARGETS={help:'#btnHelp',setup:'#btnSetup',launch:'#btnLaunch',close:'#btnClose',demo:'#btnDemo','part-tour':'#btnPartTour',settings:'#btnSettings',refresh:'#btnRefresh'};
async function loadVerbTimes(){
const box=document.getElementById('verbTimes');
box.innerHTML='<div class="note">loading...</div>';
try{
const r=await fetch('api/verb_times/'+encodeURIComponent(curTarget||''));
const d=await r.json();
const verbs=d.verbs||{};
const keys=Object.keys(verbs);
if(!keys.length){box.innerHTML='<div class="note">'+(d.error||'no runs logged yet on '+(d.target||'this box'))+'</div>';return;}
let h='<table style="width:100%;border-collapse:collapse;font-size:12.5px">'
+'<tr style="text-align:left;color:var(--text-2)"><th>verb</th><th>count</th><th>p50</th><th>p90</th><th>max</th><th>failures</th></tr>';
for(const k of keys){const v=verbs[k];
const slow=(v.p90||0)>3*(v.p50||1)&&(v.p90||0)>10; // two code paths wearing one name
h+='<tr'+(v.failures?' style="color:#f85149"':(slow?' style="color:#d29922"':''))+'>'
+'<td style="padding:3px 10px 3px 0">'+k+'</td><td>'+v.count+'</td>'
+'<td>'+(v.p50==null?'-':v.p50+'s')+'</td><td>'+(v.p90==null?'-':v.p90+'s')+'</td>'
+'<td>'+(v.max==null?'-':v.max+'s')+'</td><td>'+v.failures+'</td></tr>';}
box.innerHTML=h+'</table>';
}catch(e){box.innerHTML='<div class="note">verb times unavailable: '+e+'</div>';}
}
document.getElementById('btnVerbTimes').onclick=loadVerbTimes;
function connectSSE(){
const es=new EventSource('events');
es.addEventListener('hello',()=>{console.log('sse connected');});
es.addEventListener('state',e=>{try{const st=JSON.parse(e.data);if(!curTarget||st.target===curTarget){lastStatus=st;renderLeds(st);}}catch(_){}});
es.addEventListener('toast',e=>{try{const t=JSON.parse(e.data);toast(t.message,t.kind==='info'?'':t.kind);}catch(_){}});
es.addEventListener('surface',e=>{try{onSurfaceEvent(JSON.parse(e.data));}catch(_){}});
es.addEventListener('click',e=>{try{
const {target}=JSON.parse(e.data);const sel=CLICK_TARGETS[target];
if(sel&&$(sel)){$(sel).click();}else{toast('unknown click target: '+target,'err');}
}catch(_){}});
es.onerror=()=>{es.close();setTimeout(connectSSE,3000);};
}
connectSSE();
// Tooltips: body-appended, fixed, clamped to the viewport on BOTH axes.
const tt=$('#tt');let ttTimer=null;
document.addEventListener('mouseover',e=>{
const el=e.target.closest('[data-tip]');
if(!el){clearTimeout(ttTimer);tt.classList.remove('show');return;}
clearTimeout(ttTimer);
ttTimer=setTimeout(()=>{
tt.textContent=el.getAttribute('data-tip');
tt.style.left='0px';tt.style.top='0px';tt.classList.add('show');
const r=el.getBoundingClientRect(),M=8,w=tt.offsetWidth,h=tt.offsetHeight;
let x=r.left+r.width/2-w/2, y=r.bottom+10;
if(y+h>innerHeight-M) y=r.top-h-10;
x=Math.max(M,Math.min(x,innerWidth-w-M));
y=Math.max(M,Math.min(y,innerHeight-h-M));
tt.style.left=x+'px';tt.style.top=y+'px';
},600);
});
document.addEventListener('mouseout',e=>{if(e.target.closest('[data-tip]')){clearTimeout(ttTimer);tt.classList.remove('show');}});
// Brand hover popover: app identity lives here, not in the header row.
const ai=$('#appinfo');const brand=document.querySelector('.brand');
let aiShow=null,aiHide=null;
function fillAppInfo(){
const v=lastStatus?lastStatus.dashboardVersion:'';const bv=lastStatus?(lastStatus.bridgeVersion||'?'):'?';
ai.innerHTML='<b style="color:var(--text)">KiCad Dashboard</b><br>dashboard v'+(v||'?')+' \u00b7 bridge v'+bv+
'<br>engine: kicad-dashboard (Python) + adom-bridge<br>author: adom<br>'+
'<a href="https://wiki.adom.inc/adom/kicad-bridge" target="_blank" rel="noopener">wiki.adom.inc/adom/kicad-bridge</a>';
}
brand.addEventListener('click',()=>{fillAppInfo();ai.classList.toggle('show');
const r=brand.getBoundingClientRect(),M=8;
ai.style.left=Math.max(M,Math.min(r.left,innerWidth-ai.offsetWidth-M))+'px';
ai.style.top=Math.min(r.bottom+8,innerHeight-ai.offsetHeight-M)+'px';});
brand.addEventListener('mouseenter',()=>{clearTimeout(aiHide);aiShow=setTimeout(()=>{
fillAppInfo();ai.classList.add('show');
const r=brand.getBoundingClientRect(),M=8;
let x=Math.max(M,Math.min(r.left,innerWidth-ai.offsetWidth-M));
ai.style.left=x+'px';ai.style.top=Math.min(r.bottom+8,innerHeight-ai.offsetHeight-M)+'px';
},600);});
brand.addEventListener('mouseleave',()=>{clearTimeout(aiShow);aiHide=setTimeout(()=>ai.classList.remove('show'),220);});
ai.addEventListener('mouseenter',()=>clearTimeout(aiHide));
ai.addEventListener('mouseleave',()=>{aiHide=setTimeout(()=>ai.classList.remove('show'),220);});
// FIRST PAINT MUST NOT WAIT ON THE NETWORK (ralph iter-2: /api/targets probes
// every box, so one wedged laptop left the whole page on "connecting..." with
// no LEDs and no pills). Skeleton now, then status and targets INDEPENDENTLY.
// ── newbie-graduation (1.9.1): Fresh -> Graduated -> Recallable ────────────
// Scaffolds fold on dismissal, 3 green launches, or the first AI-driven
// action; the say strip folds LAST (5 launches). The header's ? restores
// everything for this session. Storage is per-app by origin; unreadable
// storage renders Fresh - the safe default is teaching.
const GRAD_KEY='adom-dash-graduation';
function gradState(){try{return JSON.parse(localStorage.getItem(GRAD_KEY))||{}}catch(e){return{}}}
function gradSave(patch){try{localStorage.setItem(GRAD_KEY,JSON.stringify({...gradState(),...patch}))}catch(e){}}
function recalled(){try{return sessionStorage.getItem('adom-dash-recall')==='1'}catch(e){return false}}
// The module owns the panel's open/closed memory and wires the header ?
// itself (we pass toggleButton). Nothing to graduate here any more.
// launch counting: one per page load, when the critical row goes green within 60s
let _launchCounted=false;const _loadT=Date.now();
function maybeCountLaunch(st){
if(_launchCounted||Date.now()-_loadT>60000)return;
const l=st.leds||{};
if(['relay','box','bridge','kicad'].every(k=>(l[k]||{}).state==='ok')){
_launchCounted=true;gradSave({launches:(gradState().launches||0)+1});
}
}
// ── dash-demo player: toolbar wiring ───────────────────────────────────────
const MUTE_KEY='kdDemoMuted';
// John, 2026-09-03: a mute remembered across sessions silently started every later tour without
// audio. Mute now lives for THIS page session only, and Play says so when it starts muted.
function demoMuted(){try{return sessionStorage.getItem(MUTE_KEY)==='1'}catch(e){return false}}
function setMuteUI(){$('#dtMute').classList.toggle('muted',demoMuted());}
$('#dtMute').onclick=async()=>{
const m=!demoMuted();try{sessionStorage.setItem(MUTE_KEY,m?'1':'0')}catch(e){}
setMuteUI();await act('demo_control',{cmd:m?'mute':'unmute'});
toast(m?'narration muted':'narration on','ok');
};
$('#dtPlay').onclick=()=>demoCtl($('#dtPlay').dataset.paused==='1'?'play':'pause');
$('#dtPrev').onclick=()=>demoCtl('prev');
$('#dtNext').onclick=()=>demoCtl('next');
$('#dtExit').onclick=()=>demoCtl('exit');
setMuteUI();
// Measure, never assume: report the real viewport of whatever surface we are
// rendered in (ah webview pane, browser, iframe) back to our own server, so
// `GET /api/viewport` can prove which side of the breakpoint we landed on.
(function reportViewport(){
const send=()=>{try{fetch('api/viewport',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({innerWidth:window.innerWidth,innerHeight:window.innerHeight,scrollWidth:document.documentElement.scrollWidth,overflowX:document.documentElement.scrollWidth>window.innerWidth,offenders:(()=>{const vw=window.innerWidth,out=[];document.querySelectorAll('body *').forEach(n=>{const r=n.getBoundingClientRect();if(r.right>vw+1||r.width>vw+1){out.push((n.id?'#'+n.id:(n.className&&n.className.toString().split(' ')[0]?'.'+n.className.toString().split(' ')[0]:n.tagName))+':w'+Math.round(r.width)+',r'+Math.round(r.right));}});return out.slice(0,8)})(),
dpr:window.devicePixelRatio,twoCol:window.innerWidth>700,
panelCols:(()=>{const g=document.querySelector('.anp-cols')||document.querySelector('.anp');return g?getComputedStyle(g).gridTemplateColumns:null})(),
panelOpen:!!document.querySelector('.anp'),
ariaPressed:document.getElementById('btnHelp')?document.getElementById('btnHelp').getAttribute('aria-pressed'):null,
helpLabel:document.getElementById('btnHelp')?document.getElementById('btnHelp').getAttribute('aria-label'):null,
headerH:document.querySelector('header')?Math.round(document.querySelector('header').getBoundingClientRect().height):null,
headerPadX:document.querySelector('header')?getComputedStyle(document.querySelector('header')).paddingLeft:null,
truncatedPhrases:[...document.querySelectorAll('.anp-phrase,.p-phrase')].filter(n=>n.scrollWidth>n.clientWidth).length,
totalPhrases:document.querySelectorAll('.anp-phrase,.p-phrase').length})}).catch(()=>{});}catch(e){}};
send();window.addEventListener('resize',()=>{clearTimeout(window.__vpT);window.__vpT=setTimeout(send,400);});
})();
renderSkeleton();
buildSay();
buildSurfaces();
refresh();
loadTargets();
setInterval(()=>refresh(),15000);setInterval(loadTargets,60000);
</script>
</body>
</html>