neser 1.1.0

NESER - Nintendo Emulation Systems Engine (Rust). Desktop and WebAssembly frontends.
Documentation
<!doctype html>
<html lang="en" data-theme="night">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>NESER Web</title>
    <link rel="stylesheet" href="./main.css" />
    <link rel="stylesheet" href="./debugger.css" />
  </head>
  <body>
    <!-- DaisyUI drawer: sidebar + main content -->
    <div class="drawer lg:drawer-open">
      <input id="sidebar-toggle" type="checkbox" class="drawer-toggle" />

      <!-- Main content area -->
      <div class="drawer-content flex flex-col min-h-screen">
        <!-- Top bar -->
        <header class="flex items-center gap-2 px-4 py-2 bg-base-200 border-b border-base-300 flex-wrap">
          <!-- Hamburger for mobile/tablet -->
          <label for="sidebar-toggle" class="btn btn-ghost btn-sm lg:hidden" aria-label="Open sidebar">
            <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /></svg>
          </label>
          <div class="flex flex-wrap items-center gap-2">
            <button id="screen-minus" class="btn btn-outline btn-sm">Zoom -</button>
            <button id="screen-plus" class="btn btn-outline btn-sm">Zoom +</button>
            <button id="fullscreen" class="btn btn-outline btn-sm">Fullscreen</button>
            <button id="filter-toggle" class="btn btn-outline btn-sm">Filter</button>
            <button id="mute" class="btn btn-outline btn-sm" aria-pressed="false">Mute</button>
          </div>
        </header>

        <!-- Canvas area -->
        <main class="flex-1 flex flex-col items-center justify-center p-4">
          <!-- Touch controls wrapper: D-pad | Canvas | Action buttons -->
          <div id="touch-controls" class="touch-controls" aria-hidden="true">
            <!-- D-pad (left of canvas) -->
            <div class="touch-dpad-area">
              <div class="touch-joystick" data-touch-zone="joystick" aria-label="Directional joystick">
                <div class="touch-joystick-base"></div>
                <div class="touch-joystick-knob"></div>
              </div>
            </div>

            <!-- Canvas (center) -->
            <div class="screen-wrap" id="screen-wrap">
              <canvas id="screen" width="256" height="240" tabindex="0"></canvas>
              <div id="rec-overlay" class="rec-overlay hidden" aria-hidden="true"></div>
              <div id="shortcut-help-overlay" class="shortcut-help-overlay hidden" aria-hidden="true"></div>
              <div id="debugger-panel" class="debugger-panel hidden" aria-label="Debugger panel"></div>
            </div>

            <!-- Action buttons (right of canvas) -->
            <div class="touch-action-area">
              <div class="touch-meta-buttons">
                <div class="touch-meta-btn" data-button="select" aria-label="Select button">SELECT</div>
                <div class="touch-meta-btn" data-button="start" aria-label="Start button">START</div>
              </div>
              <div class="touch-action-buttons">
                <div class="touch-btn touch-btn-b" data-button="b" aria-label="B button">B</div>
                <div class="touch-btn touch-btn-a" data-button="a" aria-label="A button">A</div>
              </div>
            </div>
          </div>

          <div class="mt-3 flex items-center gap-2">
            <span id="status" class="status"></span>
            <span id="fps-counter" class="badge badge-neutral" style="display:none"></span>
            <span class="badge badge-neutral touch-hide">Press 'h' for help</span>
          </div>
        </main>

        <!-- Footer removed -->
      </div>

      <!-- Sidebar -->
      <div class="drawer-side z-40">
        <label for="sidebar-toggle" class="drawer-overlay" aria-label="Close sidebar"></label>
        <aside class="w-60 min-h-screen bg-base-200 border-r border-base-300 flex flex-col p-4 gap-4">
          <!-- NESER ASCII art logo -->
          <div class="text-center select-none">
            <pre class="font-mono text-xs leading-tight text-[#00f0ff]"> _   _ _____ ____  _____ ____
| \ | | ____/ ___|| ____|  _ \
|  \| |  _| \___ \|  _| | |_) |
| |\  | |___ ___) | |___|  _ &lt;
|_| \_|_____|____/|_____|_| \_\</pre>
          </div>

          <!-- ROM controls -->
          <div class="flex flex-col gap-2">
            <label for="rom" class="label text-xs">ROM file</label>
            <input type="file" id="rom" accept=".nes,.gb,.gbc,.cgb,.gba,application/octet-stream" class="file-input file-input-bordered file-input-sm w-full" />
            <select id="rom-select" class="select select-bordered select-sm w-full" aria-label="Select ROM from server">
              <option value="">Select bundled ROM…</option>
            </select>
          </div>

          <!-- Autorun controls -->
          <div id="autorun-section" class="flex flex-col gap-2">
            <label class="label cursor-pointer justify-start gap-2 p-0">
              <input type="checkbox" id="autorun-create" class="checkbox checkbox-sm" disabled />
              <span class="label-text text-xs">Create autorun</span>
            </label>
            <button id="autorun-load" class="btn btn-info btn-outline btn-sm w-full" disabled>Load autorun</button>
            <ul id="autorun-status" class="text-xs text-info list-none space-y-0.5"></ul>
            <button id="autorun-cancel" class="btn btn-error btn-outline btn-sm w-full hidden" aria-label="Cancel autorun">✕ Cancel Autorun</button>
          </div>

          <!-- Emulation controls -->
          <div class="flex flex-col gap-2">
            <button id="start" class="btn btn-success btn-sm w-full">Start</button>
            <div class="flex gap-2">
              <button id="pause" class="btn btn-outline btn-sm flex-1" aria-label="Pause or resume emulation">Pause</button>
              <button id="reset" class="btn btn-warning btn-outline btn-sm flex-1" aria-label="Reset emulation">Reset</button>
            </div>
            <button id="stop" class="btn btn-error btn-outline btn-sm w-full" aria-label="Stop emulation">Stop</button>
            <div id="save-state-section" class="flex gap-2">
              <button id="save-state" class="btn btn-warning btn-outline btn-sm flex-1" disabled>Save State</button>
              <button id="load-state" class="btn btn-warning btn-outline btn-sm flex-1" disabled>Load State</button>
            </div>
          </div>

          <!-- Spacer to push content up -->
          <div class="flex-1"></div>
        </aside>
      </div>
    </div>

    <!-- Autorun modal dialog (native dialog element) -->
    <dialog id="autorun-modal" class="modal" aria-labelledby="autorun-modal-label">
      <div class="modal-box">
        <h3 class="text-lg font-bold" id="autorun-modal-label">Configure Autorun</h3>
        <div class="mt-4 flex flex-col gap-3">
          <div>
            <label for="autorun-file-input" class="label text-sm">Autorun file (.autorun)</label>
            <input type="file" id="autorun-file-input" accept=".autorun" class="file-input file-input-bordered file-input-sm w-full" />
          </div>
          <div id="autorun-file-info" class="hidden">
            <div class="alert alert-info text-sm" id="autorun-file-summary"></div>
            <div class="mt-2">
              <label for="autorun-checkpoint-select" class="label text-sm">Start from checkpoint</label>
              <select id="autorun-checkpoint-select" class="select select-bordered select-sm w-full">
                <option value="-1">From the beginning</option>
              </select>
            </div>
            <label class="label cursor-pointer justify-start gap-2 mt-2">
              <input type="checkbox" id="autorun-extend-check" class="checkbox checkbox-sm" />
              <span class="label-text text-sm">Extend autorun (record after playback ends)</span>
            </label>
          </div>
        </div>
        <div class="modal-action">
          <button type="button" id="autorun-modal-cancel" class="btn btn-ghost">Cancel</button>
          <button type="button" id="autorun-use-btn" class="btn btn-primary" disabled>Use Autorun</button>
        </div>
      </div>
      <form method="dialog" class="modal-backdrop"><button>close</button></form>
    </dialog>

    <script type="module" src="./src/app.ts"></script>
  </body>
</html>