main
John Lauer Publish 1.2.4 bfa420b 1mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Grok Bot</title>
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<style>
:root {
  --bg: #0d1117; --surface: #161b22; --surface2: #1c232c; --line: #262d36;
  --text: #e6edf3; --muted: #8b949e; --teal: #00b8b1;
  --led-green: #3fb950; --led-amber: #d29922; --led-red: #f85149;
  --sans: "Familjen Grotesk", system-ui, sans-serif;
  --body: system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, Consolas, monospace;
  --on-accent: #05221f;
  --gutter: 16px;
}
*{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:'JetBrains Mono';src:url('https://adom.inc/fonts/JetBrainsMono-Regular.woff2') format('woff2');font-weight:400}
html,body{background:var(--bg);color:var(--text);font:15px/1.5 var(--body);min-height:100vh}
*{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}
body{overflow-x:hidden}
header{
  height:44px;display:flex;align-items:center;gap:12px;
  padding:0 var(--gutter);margin:0;border-radius:0;
  background:var(--surface);border-bottom:1px solid var(--line);overflow:hidden;
}
.brand{display:flex;align-items:center;gap:8px;min-width:0;overflow:hidden;
  font:600 15px/1 var(--sans);color:var(--text);letter-spacing:-.01em;cursor:default}
.brand svg{flex:none;width:18px;height:18px}
.brand .sub{color:var(--muted);font-weight:400;font-size:13px;margin-left:8px}
.actions{display:flex;align-items:center;gap:6px;margin-left:auto;flex:none}
.iconbtn{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;
  background:none;border:1px solid transparent;border-radius:6px;color:var(--muted);cursor:pointer}
.iconbtn:hover{color:var(--teal);border-color:var(--line);background:rgba(0,184,177,.08)}
.iconbtn:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.iconbtn[aria-pressed=true]{color:var(--teal);background:rgba(0,184,177,.12)}
.target-chip{display:inline-flex;align-items:center;gap:7px;min-width:0;max-width:38vw;
  border:1px solid var(--line);border-radius:999px;padding:3px 10px;
  font:12px var(--mono);color:var(--muted);overflow:hidden}
.target-chip .tdot{width:8px;height:8px;border-radius:50%;background:var(--muted);flex:none}
.target-chip[data-state=ok] .tdot{background:var(--led-green)}
.target-chip[data-state=warn] .tdot{background:var(--led-amber)}
.target-chip[data-state=err] .tdot{background:var(--led-red)}
.target-chip #chipName{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
main{margin:0;padding:20px var(--gutter) 84px}
.leds{display:flex;flex-wrap:wrap;gap:8px;margin:4px 0 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)}
.led-pill:hover{border-color:var(--teal)}
.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=warn] .dot,.led-pill[data-state=stuck] .dot{background:var(--led-amber)}
.led-pill[data-state=err] .dot,.led-pill[data-state=bad] .dot{background:var(--led-red)}
.led-pill .st{color:var(--muted)}
@keyframes ledpulse{0%,100%{opacity:1}50%{opacity:.55}}
.led-detail-pop{position:fixed;z-index:99998;max-width:360px;background:var(--surface2);
  border:1px solid var(--line);border-radius:10px;padding:12px 14px;font-size:13px;
  color:var(--text);box-shadow:0 8px 24px rgba(0,0,0,.4)}
.led-detail-pop h4{font:600 13px var(--sans);margin:0 0 6px}
.led-detail-pop .hint{color:var(--muted);margin-top:6px;font-size:12.5px}

#demoToolbar{display:none;align-items:center;gap:4px;margin-top:10px;flex-wrap:wrap}
.dt-btn{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;
  background:none;border:1px solid transparent;border-radius:6px;color:var(--text);
  font-size:16px;line-height:1;cursor:pointer;padding:0}
.dt-btn:hover{background:var(--surface2);border-color:var(--line)}
.dt-btn:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.dt-step{font:12.5px var(--body);color:var(--muted);padding:0 6px;font-variant-numeric:tabular-nums}
.dt-btn.muted{opacity:.45}
.dt-btn.exit{color:var(--led-red)}
.caption{position:fixed;left:50%;bottom:72px;transform:translateX(-50%);z-index:99990;
  max-width:min(92vw,560px);background:rgba(13,17,23,.92);border:1px solid var(--line);
  border-radius:8px;padding:10px 14px;font:14px var(--body);color:var(--text);
  text-align:center;display:none;box-shadow:0 8px 24px rgba(0,0,0,.4)}
.hl{outline:2px solid var(--teal);outline-offset:4px;border-radius:8px;transition:outline-color .2s}

h2{font:600 17px var(--sans);color:var(--text);margin:28px 0 12px}
.note{color:var(--muted);font-size:13.5px;max-width:70ch;margin:0 0 12px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:12px}
.card{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:16px}
.card h3{font:600 15px var(--sans);margin:0 0 8px}
.card p{color:var(--muted);font-size:13.5px;margin:0 0 10px}
.card code,.mono{font:12.5px/1.45 var(--mono);color:var(--text);background:var(--surface2);
  border:1px solid var(--line);border-radius:6px;padding:8px 10px;display:block;
  white-space:pre-wrap;word-break:break-word}
.btnrow{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.btn{display:inline-flex;align-items:center;gap:8px;min-height:32px;padding:0 12px;
  border-radius:8px;border:1px solid var(--line);background:var(--surface2);
  color:var(--text);font:500 13px var(--body);cursor:pointer}
.btn:hover{border-color:var(--teal);color:var(--teal)}
.btn:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.btn.send{color:var(--teal);border-color:rgba(0,184,177,.45)}
.btn.send:hover{background:rgba(0,184,177,.12)}
video{width:100%;max-height:420px;background:#000;border-radius:8px;border:1px solid var(--line);display:block}
.prompt-list{display:flex;flex-direction:column;gap:8px}
.prompt-row{display:flex;align-items:flex-start;gap:10px;background:var(--surface2);
  border:1px solid var(--line);border-radius:8px;padding:10px 12px}
.prompt-row span{flex:1;font-size:13.5px}
.prompt-row em{color:var(--teal);font-style:normal}
#toasts{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);display:flex;
  flex-direction:column-reverse;gap:8px;z-index:400;pointer-events:none}
.toast{pointer-events:auto;background:var(--surface);border:1px solid var(--line);
  border-left:3px solid var(--teal);border-radius:8px;padding:10px 14px;font-size:14px;
  box-shadow:0 8px 24px rgba(0,0,0,.4)}
.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);border:1px solid var(--line);
  border-radius:12px;padding:0;max-width:520px;width:92vw}
dialog::backdrop{background:rgba(0,0,0,.25)}
.settings-head{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:12px 14px 12px 18px;border-bottom:1px solid var(--line)}
.settings-head h2{margin:0;font:600 15px var(--sans)}
.settings-body{padding:18px;font-size:13.5px;color:var(--muted)}
.settings-body p{margin:0 0 10px}
#appinfo{position:fixed;z-index:99998;display:none;background:var(--surface2);
  border:1px solid var(--line);border-radius:10px;padding:12px 14px;font-size:12.5px;
  color:var(--muted);min-width:240px;box-shadow:0 8px 24px rgba(0,0,0,.4)}
#appinfo.show{display:block}
#appinfo a{color:var(--teal)}
#tt{position:fixed;z-index:99999;pointer-events:none;padding:6px 9px;border-radius:6px;
  background:#0d1117;border:1px solid var(--line);color:var(--text);font:12px var(--body);
  opacity:0;transition:opacity .18s ease;max-width:280px}
#tt.show{opacity:1}
@media (max-width:700px){
  .brand .sub{display:none}
  .target-chip{max-width:30vw}
}
@media (max-width:420px){
  header{gap:8px;padding:0 10px}
  .target-chip{max-width:26vw}
}
</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">
      <path d="M4 5.2h13a2.2 2.2 0 0 1 2.2 2.2v6.2a2.2 2.2 0 0 1-2.2 2.2h-6.2L6.2 19.2v-3.4H4A2.2 2.2 0 0 1 1.8 13.6V7.4A2.2 2.2 0 0 1 4 5.2z"/>
      <circle cx="8.2" cy="10.6" r="1.7"/>
      <circle cx="14.6" cy="10.6" r="1.7"/>
      <path d="M9.9 10.6h3.0"/>
    </svg>
    Grok Bot <span class="sub">the grok bridge</span>
  </div>
  <span class="target-chip" id="targetChip" data-tip="Which machine this dashboard answers on.">
    <span class="tdot" id="chipDot"></span>
    <span id="chipName">target: this machine</span>
  </span>
  <div class="actions">
    <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.2 9.3a2.9 2.9 0 0 1 5.6 1c0 1.9-2.8 2.4-2.8 4"/><path d="M12 17.6h.01"/></svg>
    </button>
    <button class="iconbtn" id="btnSettings" aria-label="Settings" data-tip="Settings">
      <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>
  <div id="hero"></div>
  <div id="newbie"></div>
  <div id="demoToolbar" role="toolbar" aria-label="Demo player">
    <button class="dt-btn" id="dtPrev" aria-label="Previous step" data-tip="Previous step"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 5v14M6 5l8 7-8 7"/></svg></button>
    <button class="dt-btn" id="dtPlay" aria-label="Pause demo" data-tip="Play / pause"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M7 5v14M17 5v14"/></svg></button>
    <button class="dt-btn" id="dtNext" aria-label="Next step" data-tip="Next step"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 5v14M18 5l-8 7 8 7"/></svg></button>
    <button class="dt-btn" id="dtMute" aria-label="Mute narration" data-tip="Mute / unmute"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 10v4h3l5 4V6L7 10H4zM17 9l4 6M21 9l-4 6"/></svg></button>
    <span class="dt-step" id="dtStep">step 1/8</span>
    <button class="dt-btn exit" id="dtExit" aria-label="Exit demo" data-tip="Exit demo"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
  </div>

  <div class="leds" id="ledRow" aria-label="Status"></div>


  <section class="card" id="card-bridge" style="margin-top:16px">
    <h3>Adom Bridge on your laptop</h3>
    <p>Grok Bot auto-connects to Adom Bridge on first use — you should never copy JSON or click through a Connections panel. If the Adom Bridge LED is not green, send this to the AI.</p>
    <code>Run grok-dashboard ensure-desktop (it calls adom-bridge setup_desktop and reconnects Adom Bridge on the laptop using the public relay URL). Do not ask me to paste JSON. Do not steal focus. Never paste webhook keys. Never kill Cursor.</code>
    <div class="btnrow">
      <button class="btn" data-copy="Run grok-dashboard ensure-desktop so Grok Bot auto-connects to Adom Bridge on my laptop. Call adom-bridge setup_desktop if needed. Prefer the public relay URL in ~/.adom/relay-public-url. Do not ask me to paste JSON. Do not steal focus. Never paste webhook keys. Never kill Cursor.">Copy</button>
      <button class="btn send" data-send="Run grok-dashboard ensure-desktop so Grok Bot auto-connects to Adom Bridge on my laptop. Call adom-bridge setup_desktop if needed. Prefer the public relay URL in ~/.adom/relay-public-url. Do not ask me to paste JSON. Do not steal focus. Never paste webhook keys. Never kill Cursor.">Send to AI</button>
    </div>
  </section>

  <section class="card" id="card-demo" style="margin-top:20px">
    <h3>Demo video</h3>
    <p>Two directions in about 30 seconds. Adom Hydrogen → Grok Bot: wiki, Fusion, KiCad, Gmail, Arduino Uno. Grok Bot → Adom Hydrogen: webhook answers land in an Adom Hydrogen thread. Play the 30s demo from the panel above to walk the dashboard; this clip is the picture of that loop.</p>
    <video id="demoVideo" controls playsinline preload="metadata" poster="">
      <source src="demo.mp4" type="video/mp4">
    </video>
  </section>

  <h2>Sample AI prompts</h2>
  <p class="note">The Say it strip is primary. These are the power-user verbs behind those phrases.</p>
  <div class="prompt-list" id="powerPrompts"></div>

  <div class="grid" style="margin-top:20px">
    <section class="card" id="card-install">
      <h3>Install from scratch</h3>
      <p>Skills pack first, then the desktop runtime from the wiki manifest. Skills ≠ runtime.</p>
      <code>adom-wiki pkg install adom/grok-bridge
adom-bridge bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/v1/pages/grok-bridge/files/adom-bridge-grok-manifest.json"}'</code>
      <div class="btnrow">
        <button class="btn" data-copy="adom-wiki pkg install adom/grok-bridge&#10;adom-bridge bridge_install '{&quot;manifestUrl&quot;:&quot;https://wiki.adom.inc/api/v1/pages/grok-bridge/files/adom-bridge-grok-manifest.json&quot;}'">Copy</button>
        <button class="btn send" data-send="Install grok from scratch: run adom-wiki pkg install adom/grok-bridge, then adom-bridge bridge_install with manifestUrl https://wiki.adom.inc/api/v1/pages/grok-bridge/files/adom-bridge-grok-manifest.json">Send to AI</button>
      </div>
    </section>

    <section class="card" id="card-webhook">
      <h3>Auto webhook</h3>
      <p>Scrapes the Grok Bridge webhook routine pane (URL + key + header) and writes the local bind file. Never show or paste the key.</p>
      <code>grok_webhook_ensure {}</code>
      <div class="btnrow">
        <button class="btn" data-copy="Call grok_webhook_ensure {} so grok-bridge scrapes the Grok Bridge webhook routine pane itself. Do not ask me to copy the URL or key.">Copy</button>
        <button class="btn send" data-send="Call grok_webhook_ensure {} so grok-bridge scrapes the Grok Bridge webhook routine pane itself. Do not ask me to copy the URL or key.">Send to AI</button>
      </div>
    </section>

    <section class="card" id="card-bots">
      <h3>New bot threads</h3>
      <p>Creates a named Grok Bot agent. Inbound still lands in the grok-bridge mailbox; pass <em>agent</em> on fire to relay.</p>
      <code>grok_bot_create {"name":"Adom Echo"}</code>
      <div class="btnrow">
        <button class="btn" data-copy="Call grok_bot_create {&quot;name&quot;:&quot;Adom Echo&quot;}. Inbound still lands in the grok-bridge mailbox; pass agent on grok_webhook_fire to relay.">Copy</button>
        <button class="btn send" data-send="Call grok_bot_create {name:'Adom Echo'}. Inbound still lands in the grok-bridge mailbox; pass agent on grok_webhook_fire to relay.">Send to AI</button>
      </div>
    </section>

    <section class="card" id="card-loop">
      <h3>Two directions: Adom Hydrogen → Grok Bot vs Grok Bot → Adom Hydrogen</h3>
      <p>Adom Hydrogen → Grok Bot sends work (open Fusion, screenshot, launch apps). Grok Bot → Adom Hydrogen talks back into an Adom Hydrogen thread. There is no SendMessage / ListAgents API this dashboard can call. The working loop is fire grok_webhook_fire, then poll grok_chat_read.</p>
      <code>grok_webhook_fire {"event":"ping","payload":{"nonce":"GB-DEMO-1"}}
# then poll grok_chat_read for the nonce (~60–120s FIFO)</code>
      <div class="btnrow">
        <button class="btn" data-copy="There is no public Grok HTTP API. Fire grok_webhook_fire {event:'ping', payload:{nonce:'GB-DEMO-1'}} then poll grok_chat_read until that nonce appears (~60-120s, FIFO). Do not invent a SendMessage API. Optional: set up a monitor that repeats this loop — as a prompt, not a fake API.">Copy</button>
        <button class="btn send" data-send="There is no public Grok HTTP API. Fire grok_webhook_fire {event:'ping', payload:{nonce:'GB-DEMO-1'}} then poll grok_chat_read until that nonce appears (~60-120s, FIFO). Do not invent a SendMessage API.">Send to AI</button>
      </div>
    </section>

    <section class="card" id="card-snapshots">
      <h3>Snapshots of Grok's computer</h3>
      <p>Grok does not screenshot the agent box from Grok Bot.exe. The box <em>is</em> Grok Bot's computer. <code style="display:inline;padding:2px 6px">grok_screenshot</code> captures the Grok Bot.exe <em>window</em> on the desktop — different from a screenshot of the agent computer.</p>
      <code>Have grok-bridge screenshot its computer (the agent box).
If you meant the Grok Bot.exe window, call grok_screenshot {} instead.</code>
      <div class="btnrow">
        <button class="btn" data-copy="Screenshot grok's computer — the agent box Grok Bot drives, not the Grok Bot.exe window. If you need the host-app window, call grok_screenshot {} and label it as the Grok Bot.exe window.">Copy</button>
        <button class="btn send" data-send="Screenshot grok's computer — the agent box Grok Bot drives, not the Grok Bot.exe window. If you need the host-app window, call grok_screenshot {} and label it as the Grok Bot.exe window.">Send to AI</button>
      </div>
    </section>

    <section class="card" id="card-launch">
      <h3>Launch apps on Grok's computer</h3>
      <p>Open KiCad and viewers on the agent computer. Arduino Uno board path is already on the box.</p>
      <code>Open KiCad on grok's computer with
$HOME/kicad-offload/Arduino_Uno/Arduino_Uno.kicad_pcb</code>
      <div class="btnrow">
        <button class="btn" data-copy="Open KiCad on grok's computer with $HOME/kicad-offload/Arduino_Uno/Arduino_Uno.kicad_pcb. Stay in the background; do not steal focus.">Copy</button>
        <button class="btn send" data-send="Open KiCad on grok's computer with $HOME/kicad-offload/Arduino_Uno/Arduino_Uno.kicad_pcb. Stay in the background; do not steal focus.">Send to AI</button>
      </div>
    </section>

    <section class="card" id="card-kicad">
      <h3>KiCad offload</h3>
      <p>Grok Bot's computer can run KiCad so the EE does not have to use their laptop. <code style="display:inline;padding:2px 6px">kicad</code> is already on PATH on the box historically; install it if the LED is not green.</p>
      <code>If KiCad is missing on grok's computer, install it there.
Then open the Arduino Uno board in the background.</code>
      <div class="btnrow">
        <button class="btn" data-copy="If KiCad is missing on grok's computer, install it there so the EE does not have to use their laptop. Then open $HOME/kicad-offload/Arduino_Uno/Arduino_Uno.kicad_pcb in the background.">Copy</button>
        <button class="btn send" data-send="If KiCad is missing on grok's computer, install it there so the EE does not have to use their laptop. Then open $HOME/kicad-offload/Arduino_Uno/Arduino_Uno.kicad_pcb in the background.">Send to AI</button>
      </div>
    </section>

    <section class="card" id="card-core">
      <h3>adom/core bootstrap</h3>
      <p>Bootstrap a standard Adom workstation on Grok's computer in one command (skills hub, wiki, KiCad/Fusion skills).</p>
      <code>adom-wiki pkg install adom/core</code>
      <div class="btnrow">
        <button class="btn" data-copy="On grok's computer, run: adom-wiki pkg install adom/core">Copy</button>
        <button class="btn send" data-send="On grok's computer, run: adom-wiki pkg install adom/core">Send to AI</button>
      </div>
    </section>
  </div>
</main>

<div class="caption" id="abCaption" role="status"></div>
<div id="toasts"></div>
<div id="tt"></div>
<div id="appinfo"></div>
<div id="ledDetail" class="led-detail-pop" style="display:none"></div>

<dialog id="dlgSettings" class="settings" aria-labelledby="settingsTitle">
  <div class="settings-head">
    <h2 id="settingsTitle">Grok Bot settings</h2>
    <button class="iconbtn" id="btnCloseSettings" aria-label="Close settings"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
  </div>
  <div class="settings-body">
    <p>This page is the Grok Bot control panel. Drive grok by talking to your AI — the buttons copy or send prompts; they do not call Grok themselves.</p>
    <p>Dashboard <span class="mono" style="display:inline;padding:2px 6px" id="setVer">—</span> · port 8791 · wiki <a href="https://wiki.adom.inc/adom/grok-bridge" style="color:var(--teal)">adom/grok-bridge</a></p>
    <p>Never steal focus. Never paste webhook keys. Never kill Cursor.</p>
  </div>
</dialog>

<script src="newbie-panel.js"></script>
<script>
(function(){
  const $ = (s, r) => (r||document).querySelector(s);
  const $$ = (s, r) => [...(r||document).querySelectorAll(s)];
  window.__gbBoot = {complete:false, failed:[], panelMounted:false, ledTimer:null};

  function toast(kind, msg){
    if (window.AdomNewbiePanel && AdomNewbiePanel.toast) return AdomNewbiePanel.toast(msg, kind==='success'?'ok':kind);
    const host = $('#toasts');
    const t = document.createElement('div');
    t.className = 'toast ' + (kind==='success'||kind==='ok'?'ok':kind==='error'||kind==='err'?'err':'');
    t.textContent = msg; host.appendChild(t);
    setTimeout(()=>t.remove(), kind==='err'?7000:4000);
  }

  function copyPhrase(text){
    const done = () => toast('ok', 'Copied to clipboard. Paste it into any AI thread.');
    const legacy = () => {
      const ta = Object.assign(document.createElement('textarea'), {value:text});
      ta.style.cssText = 'position:fixed;opacity:0'; document.body.append(ta);
      ta.select(); const ok = document.execCommand('copy'); ta.remove(); return ok;
    };
    if (window.AdomNewbiePanel && AdomNewbiePanel.copyText){
      return AdomNewbiePanel.copyText(text).then(done, () => { if (legacy()) done(); else toast('err','Could not reach the clipboard.'); });
    }
    if (document.hasFocus() && navigator.clipboard && navigator.clipboard.writeText){
      navigator.clipboard.writeText(text).then(done, () => { if (legacy()) done(); });
    } else if (legacy()) done();
    else toast('err','Could not reach the clipboard.');
  }

  async function sendPhrase(phrase){
    const r = await fetch('/api/say-send', {method:'POST', headers:{'Content-Type':'application/json'},
      body: JSON.stringify({phrase})}).then(x=>x.json()).catch(()=>null);
    if (r && r.ok) toast('ok', 'Sent to a new AI thread.');
    else if (r && r.unconfirmed) toast('info', r.why || 'Sent, still confirming.');
    else { copyPhrase(phrase); toast('err', (r && r.why) || 'Could not reach the AI panel; copied instead.'); }
  }

  const POWER = [
    ['grok_webhook_fire ping', 'Fire grok_webhook_fire {event:"ping", payload:{nonce:"GB-DEMO-1"}} then poll grok_chat_read for the nonce.'],
    ['grok_bot_create', 'Call grok_bot_create {name:"Adom Echo"}.'],
    ['grok_chat_read', 'Call grok_chat_status then grok_chat_read and tell me if Grok Bot is thinking.'],
    ['screenshot grok computer', "Screenshot grok's computer — the agent box, not the Grok Bot.exe window."],
    ['kicad_open_board on grok', 'Open KiCad on grok\'s computer with $HOME/kicad-offload/Arduino_Uno/Arduino_Uno.kicad_pcb.']
  ];
  $('#powerPrompts').innerHTML = POWER.map(([label,p]) =>
    `<div class="prompt-row"><span><em>${label}</em> — ${p}</span>`+
    `<button class="btn" data-copy="${p.replace(/"/g,'&quot;')}">Copy</button>`+
    `<button class="btn send" data-send="${p.replace(/"/g,'&quot;')}">Send to AI</button></div>`
  ).join('');

  document.addEventListener('click', e => {
    const c = e.target.closest('[data-copy]');
    const s = e.target.closest('[data-send]');
    if (s){ e.preventDefault(); sendPhrase(s.getAttribute('data-send')); return; }
    if (c){ e.preventDefault(); copyPhrase(c.getAttribute('data-copy')); }
  });

  let lastStatus = null;
  function renderLeds(st){
    lastStatus = st;
    const row = $('#ledRow');
    const leds = st.leds || [];
    row.innerHTML = leds.map(l => {
      const stt = l.state==='err'?'bad':l.state;
      return `<button class="led-pill" data-state="${stt}" data-id="${l.id}">`+
        `<span class="dot"></span><span>${l.label}</span> <span class="st">${l.short||l.state}</span></button>`;
    }).join('');
    const chip = $('#targetChip');
    chip.dataset.state = st.state==='err'?'err':st.state==='warn'?'warn':'ok';
    const t = st.target || 'this machine';
    $('#chipName').textContent = 'target: ' + t;
    $('#setVer').textContent = st.dashboardVersion || (window.__DASH_VERSION__||'?');
    row.querySelectorAll('.led-pill').forEach(btn => {
      btn.onclick = () => {
        const led = leds.find(x => x.id===btn.dataset.id); if(!led) return;
        const pop = $('#ledDetail');
        pop.style.display = 'block';
        pop.innerHTML = `<h4>${led.label}</h4><div>${led.detail||led.short||led.state}</div><div class="hint">${led.hint||''}</div>`;
        const r = btn.getBoundingClientRect();
        pop.style.left = Math.min(r.left, innerWidth-380) + 'px';
        pop.style.top = (r.bottom+8) + 'px';
      };
    });
  }
  document.addEventListener('click', e => {
    if (!e.target.closest('.led-pill') && !e.target.closest('#ledDetail'))
      $('#ledDetail').style.display = 'none';
  });

  async function refresh(force){
    try{
      const st = await fetch('/api/status'+(force?'/force':'')).then(r=>r.json());
      renderLeds(st);
    }catch(e){ /* first paint stays empty rather than lying */ }
  }

  // ----- newbie panel -----
  function doMount(phrases){
    AdomNewbiePanel.mount({
      mount:'#newbie',
      app:'grok',
      appName:'Grok Bot',
      slug:'grok-bridge',
      toggleButton:'#btnHelp',
      demo:{label:'Play the 30s demo', meta:'8 steps, about 30 seconds',
            onPlay:function(){ startDemo(); }},
      phrases:phrases.slice(0,6)
    });
    window.__gbBoot.panelMounted = true;
    const left = document.querySelector('#newbie .anp-left');
    const bar = $('#demoToolbar');
    if (left && bar && bar.parentElement !== left) left.appendChild(bar);
  }
  function buildSay(){
    const phrases = (window.__SAY_PHRASES__||[]).slice(0,6);
    if (!phrases.length){ window.__gbBoot.failed.push('no phrases'); return; }
    const attempt = () => {
      if (!window.AdomNewbiePanel) return false;
      try{ doMount(phrases); }catch(e){ console.error('newbie panel mount failed', e); window.__gbBoot.failed.push(String(e)); }
      return true;
    };
    if (attempt()) return;
    let tries=0;
    const t=setInterval(()=>{ if(attempt()||++tries>20) clearInterval(t); },250);
  }

  // ----- dash-demo player (in-page; captions in the webview) -----
  const MUTE_KEY = 'gbDemoMuted';
  let steps = [];
  let demo = {on:false, i:0, timer:null, audio:null, paused:false, started:0, remain:0};
  function demoMuted(){ try{ return localStorage.getItem(MUTE_KEY)==='1'; }catch(e){ return false; } }
  function setMuteUI(){ $('#dtMute').classList.toggle('muted', demoMuted()); }
  async function loadSteps(){
    try{ const d = await fetch('/demo/steps.json').then(r=>r.json()); steps = Array.isArray(d)?d:(d.steps||[]); }
    catch(e){ steps = []; }
  }
  function clearHighlight(){ $$('.hl').forEach(n=>n.classList.remove('hl')); }
  function showCaption(text){
    const c = $('#abCaption');
    if (!text){ c.style.display='none'; c.textContent=''; return; }
    c.textContent = text; c.style.display='block';
  }
  function stopAudio(){
    if (demo.audio){ try{ demo.audio.pause(); demo.audio.src=''; }catch(e){} demo.audio=null; }
  }
  function runAction(step){
    clearHighlight();
    const a = step.action||'';
    if (a.startsWith('highlight:')){
      const el = $(a.slice(10)); if (el){ el.classList.add('hl'); el.scrollIntoView({block:'nearest', behavior:'smooth'}); }
    } else if (a.startsWith('play-video:')){
      const v = $(a.slice(11));
      if (v){ v.currentTime = 0; const p = v.play(); if (p&&p.catch) p.catch(()=>{}); }
    }
  }
  function playStep(i){
    if (!demo.on) return;
    if (i<0) i=0;
    if (i>=steps.length){ exitDemo(); return; }
    demo.i = i;
    const step = steps[i];
    $('#dtStep').textContent = 'step '+(i+1)+'/'+steps.length;
    $('#dtPlay').innerHTML = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M7 5v14M17 5v14"/></svg>';
    showCaption(step.caption||'');
    runAction(step);
    stopAudio();
    const src = step.audio ? '/' + String(step.audio).replace(/^\//,'') : '';
    if (src && !demoMuted()){
      const au = new Audio(src);
      demo.audio = au;
      au.play().catch(()=>{});
    }
    const floor = step.min_ms || 4000;
    demo.started = Date.now(); demo.remain = floor;
    clearTimeout(demo.timer);
    demo.timer = setTimeout(()=>playStep(i+1), floor);
  }
  function startDemo(){
    if (!steps.length){ toast('err','Demo steps missing'); return; }
    demo.on = true; demo.paused = false;
    $('#demoToolbar').style.display = 'flex';
    const btn = document.querySelector('.anp-demo');
    if (btn){ btn.disabled = true; const lab = btn.querySelector('span'); if (lab) lab.textContent = 'Demo running'; }
    playStep(0);
  }
  function pauseDemo(){
    if (!demo.on) return;
    demo.paused = !demo.paused;
    if (demo.paused){
      clearTimeout(demo.timer);
      demo.remain = Math.max(200, demo.remain - (Date.now()-demo.started));
      if (demo.audio) try{ demo.audio.pause(); }catch(e){}
      const v = $('#demoVideo'); if (v && !v.paused) v.pause();
      $('#dtPlay').innerHTML = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 4l13 8-13 8z"/></svg>';
    } else {
      $('#dtPlay').innerHTML = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M7 5v14M17 5v14"/></svg>';
      if (demo.audio && !demoMuted()) try{ demo.audio.play(); }catch(e){}
      demo.started = Date.now();
      demo.timer = setTimeout(()=>playStep(demo.i+1), demo.remain);
    }
  }
  function exitDemo(){
    demo.on = false; demo.paused = false;
    clearTimeout(demo.timer); stopAudio(); clearHighlight(); showCaption('');
    const v = $('#demoVideo'); if (v){ try{ v.pause(); }catch(e){} }
    $('#demoToolbar').style.display = 'none';
    const btn = document.querySelector('.anp-demo');
    if (btn){ btn.disabled = false; const lab = btn.querySelector('span'); if (lab) lab.textContent = 'Play the 30s demo'; }
  }
  $('#dtPrev').onclick = ()=>{ if(demo.on) playStep(demo.i-1); };
  $('#dtNext').onclick = ()=>{ if(demo.on) playStep(demo.i+1); };
  $('#dtPlay').onclick = ()=>pauseDemo();
  $('#dtExit').onclick = ()=>exitDemo();
  $('#dtMute').onclick = ()=>{
    const m = !demoMuted(); try{ localStorage.setItem(MUTE_KEY, m?'1':'0'); }catch(e){}
    setMuteUI();
    if (m) stopAudio();
    toast('ok', m?'narration muted':'narration on');
  };
  setMuteUI();

  // settings + brand popover
  $('#btnSettings').onclick = ()=>$('#dlgSettings').showModal();
  $('#btnCloseSettings').onclick = ()=>$('#dlgSettings').close();
  $('#dlgSettings').addEventListener('click', e => { if (e.target===$('#dlgSettings')) $('#dlgSettings').close(); });
  const ai = $('#appinfo'), brand = $('#brand');
  function fillAppInfo(){
    const v = (lastStatus && lastStatus.dashboardVersion) || window.__DASH_VERSION__ || '?';
    ai.innerHTML = '<b style="color:var(--text)">Grok Bot</b><br>dashboard v'+v+
      '<br>engine: grok-dashboard (Python) + adom-bridge<br>'+
      '<a href="https://wiki.adom.inc/adom/grok-bridge" target="_blank" rel="noopener">wiki.adom.inc/adom/grok-bridge</a>';
  }
  let aiShow=null, aiHide=null;
  brand.addEventListener('mouseenter', ()=>{ clearTimeout(aiHide); aiShow=setTimeout(()=>{
    fillAppInfo(); ai.classList.add('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';
  },600);});
  brand.addEventListener('mouseleave', ()=>{ clearTimeout(aiShow); aiHide=setTimeout(()=>ai.classList.remove('show'),220); });
  brand.addEventListener('click', ()=>{ fillAppInfo(); ai.classList.toggle('show'); });

  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');}});

  (function reportViewport(){
    const send=()=>{try{fetch('/api/viewport',{method:'POST',headers:{'Content-Type':'application/json'},
      body:JSON.stringify({innerWidth:innerWidth,innerHeight:innerHeight,
        scrollWidth:document.documentElement.scrollWidth,
        overflowX:document.documentElement.scrollWidth>innerWidth,
        dpr:devicePixelRatio, panelOpen:!!document.querySelector('.anp:not([hidden])')})}).catch(()=>{});}catch(e){}};
    send(); addEventListener('resize', ()=>{ clearTimeout(window.__vpT); window.__vpT=setTimeout(send,400); });
  })();

  try{ buildSay(); }catch(e){ window.__gbBoot.failed.push('say:'+e); }
  try{ loadSteps(); }catch(e){ window.__gbBoot.failed.push('steps:'+e); }
  try{ refresh(); window.__gbBoot.ledTimer = setInterval(()=>refresh(), 15000); }catch(e){ window.__gbBoot.failed.push('leds:'+e); }
  window.__gbBoot.complete = true;
})();
</script>
</body>
</html>