Quellcode durchsuchen

panel: top action bar + wider single-column layout

Add a sticky top bar with the connection button/state and the
device-maintenance actions (Serial DFU, UF2 DFU, Factory erase), moved out
of the Link-status card (same IDs, JS wiring unchanged). Widen the wrap
680->980px and make the Link-status stat grid auto-fill so tiles spread
across the width instead of a fixed 3 columns. Cards stay in one vertical
column (no side-by-side panels); horizontal space is used within cards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Jari vor 3 Monaten
Ursprung
Commit
3dc5d95998
1 geänderte Dateien mit 36 neuen und 36 gelöschten Zeilen
  1. 36 36
      docs/index.html

+ 36 - 36
docs/index.html

@@ -7,31 +7,41 @@
 <style>
   :root{ --bg:#11131a; --card:#1b1f2b; --fg:#e7ecf3; --mut:#8a93a6; --acc:#5bd6a0; --acc2:#5b9bd6; --bad:#d65b6e; }
   *{box-sizing:border-box}
-  body{margin:0;font:14px/1.5 -apple-system,Segoe UI,Roboto,sans-serif;background:var(--bg);color:var(--fg);padding:24px}
+  body{margin:0;font:14px/1.5 -apple-system,Segoe UI,Roboto,sans-serif;background:var(--bg);color:var(--fg)}
   h1{font-size:20px;margin:0 0 4px}
-  .sub{color:var(--mut);margin:0 0 20px}
-  .wrap{max-width:680px;margin:0 auto}
-  .card{background:var(--card);border:1px solid #272d3d;border-radius:12px;padding:18px 20px;margin-bottom:16px}
+  .sub{color:var(--mut);margin:0 0 18px;font-size:13px}
+  /* sticky top action bar: connection + device-maintenance actions */
+  .topbar{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:10px;flex-wrap:wrap;
+    padding:9px 18px;background:#151925;border-bottom:1px solid #272d3d}
+  .topbar .brand{font-weight:700;font-size:15px;letter-spacing:.02em}
+  .topbar .brand small{color:var(--mut);font-weight:400;font-size:11px;margin-left:6px}
+  .topbar .spacer{flex:1 1 16px}
+  .topbar button{padding:7px 12px}
+  .wrap{max-width:980px;margin:0 auto;padding:20px}
+  /* cards stack in one column; width comes from the wider wrap + grids/rows filling it horizontally */
+  .card{background:var(--card);border:1px solid #272d3d;border-radius:12px;padding:16px 18px;margin:0 0 16px}
   .card h2{font-size:13px;text-transform:uppercase;letter-spacing:.06em;color:var(--mut);margin:0 0 14px}
-  .row{display:flex;align-items:center;gap:14px;margin:12px 0}
+  .row{display:flex;align-items:center;gap:14px;margin:12px 0;flex-wrap:wrap}
   .row label{flex:0 0 150px;color:var(--fg)}
   .row .val{flex:0 0 64px;text-align:right;color:var(--acc);font-variant-numeric:tabular-nums}
-  input[type=range]{flex:1;accent-color:var(--acc)}
-  select{background:#0e1017;color:var(--fg);border:1px solid #313a4f;border-radius:8px;padding:6px 8px;flex:1}
+  input[type=range]{flex:1;min-width:120px;accent-color:var(--acc)}
+  select{background:#0e1017;color:var(--fg);border:1px solid #313a4f;border-radius:8px;padding:6px 8px;flex:1;min-width:120px}
   button{background:#222a3a;color:var(--fg);border:1px solid #36405a;border-radius:8px;padding:9px 14px;cursor:pointer;font:inherit}
   button:hover{border-color:var(--acc2)}
   button.primary{background:var(--acc2);border-color:var(--acc2);color:#06121f;font-weight:600}
   button.active{background:var(--acc);border-color:var(--acc);color:#062018;font-weight:600}
+  button.danger{background:#3a1420;border-color:#7a2233;color:#ff9bb0;font-weight:600}
+  button.danger:hover{border-color:#ff9bb0}
   button:disabled{opacity:.4;cursor:default}
   .pill{display:inline-block;padding:2px 9px;border-radius:99px;font-size:12px;font-weight:600}
   .pill.up{background:#143626;color:var(--acc)} .pill.dn{background:#3a1822;color:var(--bad)}
-  .statgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
+  .statgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(155px,1fr));gap:10px}
   .stat{background:#0e1017;border:1px solid #232a3a;border-radius:8px;padding:10px}
   .stat .k{color:var(--mut);font-size:11px;text-transform:uppercase;letter-spacing:.05em}
   .stat .v{font-size:18px;margin-top:2px}
-  .modes{display:flex;gap:10px}
+  .modes{display:flex;gap:10px;flex-wrap:wrap}
   .note{color:var(--mut);font-size:12px;margin-top:10px}
-  #log{font:12px ui-monospace,Menlo,monospace;color:var(--mut);white-space:pre-wrap;margin-top:8px;max-height:120px;overflow:auto}
+  #log{font:12px ui-monospace,Menlo,monospace;color:var(--mut);white-space:pre-wrap;margin-top:8px;max-height:140px;overflow:auto}
   .hide{display:none}
 
   /* ---- controller slot tabs ---- */
@@ -50,19 +60,20 @@
 </style>
 </head>
 <body>
+<div class="topbar">
+  <span class="brand">OpenPuck <small>SC2 puck · WebUSB</small></span>
+  <span id="connState" class="pill dn">disconnected</span>
+  <button id="connectBtn" class="primary">Connect</button>
+  <span class="spacer"></span>
+  <button id="dfuSerial" title="Reboot into serial DFU (adafruit-nrfutil), then replug">Serial DFU</button>
+  <button id="dfuUf2" title="Reboot into the UF2 mass-storage bootloader; drop a .uf2 on the drive">UF2 DFU</button>
+  <button id="factoryErase" class="danger" title="Wipe ALL bonds + settings and reboot to defaults. You must re-pair. Cannot be undone.">⚠ Factory erase</button>
+</div>
 <div class="wrap">
-  <h1>OpenPuck Config</h1>
-  <p class="sub">nRF52840 Steam Controller 2 puck reimplementation — WebUSB. Chrome/Edge only.</p>
-
-  <div class="card" id="connectCard">
-    <div class="row" style="margin:0">
-      <button id="connectBtn" class="primary">Connect to copycat</button>
-      <span id="connState" class="pill dn">disconnected</span>
-    </div>
-    <p class="note">If nothing appears in the device picker, the copycat may be busy (quit any app holding it) or needs a replug.</p>
-  </div>
+  <p class="sub">First connect uses the Chrome/Edge device picker; reconnects are automatic. If the picker is empty, quit any app holding the device or replug.</p>
 
   <div id="panel" class="hide">
+    <div class="cards">
     <div class="card">
       <h2>USB mode</h2>
       <div class="modes" style="flex-wrap:wrap">
@@ -89,7 +100,7 @@
       <div class="row"><label>Y + back4 →</label><select class="chord" data-i="2"></select></div>
     </div>
 
-    <div class="card">
+    <div class="card wide">
       <h2>Link status</h2>
 
       <!-- Per-controller tabs: visible only when 2+ controllers are bonded -->
@@ -134,18 +145,6 @@
         <span class="v" id="stabStatus" style="flex:0 0 auto;min-width:120px">—</span>
         <span class="note" style="flex:1">Buzzes the controllers every 10 s (keeps them awake) and times how long the puck stays up. On a reset it records the uptime and shows it by <em>Last reset</em>; it auto-resumes after the puck reconnects. The count is kept in this tab (a page refresh clears it).</span>
       </div>
-      <div class="row" style="margin:10px 0 0;border-top:1px solid #272d3d;padding-top:14px">
-        <button id="dfuSerial">Reboot to Serial DFU</button>
-        <span class="note" style="flex:1">Reboots the puck into serial DFU mode (adafruit-nrfutil). The device drops off USB — flash with <code>adafruit-nrfutil dfu serial …</code> then replug.</span>
-      </div>
-      <div class="row" style="margin:10px 0 0">
-        <button id="dfuUf2">Reboot to UF2 Bootloader</button>
-        <span class="note" style="flex:1">Reboots the puck into the UF2 mass-storage bootloader. It mounts as a USB drive — drag the <code>.uf2</code> file onto it to flash, then it reboots automatically.</span>
-      </div>
-      <div class="row" style="margin:18px 0 0;border-top:1px solid #4a2030;padding-top:14px">
-        <button id="factoryErase" style="background:#3a1420;border-color:#7a2233;color:#ff9bb0;font-weight:600">⚠ Factory erase</button>
-        <span class="note" style="flex:1"><strong style="color:#ff9bb0">Danger:</strong> wipes <em>all</em> persistent storage on the copycat — the paired controller bond <em>and</em> every saved setting (mode, chords, back-paddle map, sensitivity). The copycat reboots to factory defaults and you must <strong>re-pair the controller</strong>. This cannot be undone.</span>
-      </div>
     </div>
 
     <div class="card" id="mouseCard">
@@ -169,7 +168,7 @@
       <p class="note">Gyro sensitivity multiplier for Switch Pro mode (1.0× = unchanged).</p>
     </div>
 
-    <div class="card">
+    <div class="card wide">
       <h2>Button mapping</h2>
       <p class="note" style="margin-top:0">Separate mappings per emulated controller type — each only offers targets that exist on that controller. The type matching the current mode is highlighted. Steam &amp; Lizard are native puck modes (Steam does its own remapping) and aren't configured here.</p>
       <div id="typeCfgs"></div>
@@ -186,7 +185,7 @@
       <p class="note">Import <strong>overwrites</strong> all pairings and settings on the connected puck, then reboots it (~2 s — reconnect after). Tip: pair every controller to one puck, export once, then import onto each spare.</p>
     </div>
 
-    <div class="card">
+    <div class="card wide">
       <h2>Hang log</h2>
       <p class="note" style="margin-top:0">One row per reset: uptime before it, reason, the stuck loop stage and captured PC (when the watchdog ISR could run), and usbd stack free. Accumulates across resets in this tab — a page refresh clears it. Pair with <em>Test stability</em> above for unattended runs.</p>
       <div class="row" style="margin:0 0 8px;flex-wrap:wrap">
@@ -197,7 +196,7 @@
       <pre id="hangOut" style="font:12px ui-monospace,Menlo,monospace;color:var(--fg);background:#0e1017;border:1px solid #232a3a;border-radius:8px;padding:10px;margin:0;max-height:260px;overflow:auto;white-space:pre"></pre>
     </div>
 
-    <div class="card logonly">
+    <div class="card wide logonly">
       <h2>Capture — host → controller commands</h2>
       <p class="note" style="margin-top:0">The firmware logs everything from boot into a big RAM ring: Steam's writes (<code>ifN</code>), the frames we transmit to the controller (<code>TX→ctlr</code>), and RF <code>LINK UP/DOWN</code> edges. For the reconnect buzz: trigger it (it appears moments after the puck boots / the controller reconnects), then connect here and click <b>Dump from boot</b> — the trigger is still in the ring. (Only present in a logging build, <code>-DOPK_LOG=1</code>.)</p>
       <div class="row" style="margin:0;flex-wrap:wrap">
@@ -208,6 +207,7 @@
       <p class="note">Dump streams the whole ring (oldest→newest) and then keeps live-updating until you press Stop. <code>cmd</code> is the command/report byte, then the raw bytes.</p>
       <pre id="capOut" style="font:12px ui-monospace,Menlo,monospace;color:var(--fg);background:#0e1017;border:1px solid #232a3a;border-radius:8px;padding:10px;margin-top:10px;max-height:260px;overflow:auto;white-space:pre-wrap"></pre>
     </div>
+    </div><!-- .cards -->
 
     <div id="log"></div>
   </div>