exl100

Émulateur Exelvision EXL 100 (1984) dans le navigateur : dsp-cpp en WebAssembly, cartouches et cassettes BASIC
git clone https://git.schurger.org/exl100.git
Log | Files | Refs | README

commit 92da98c03c9de96e218c2a3d063f0ceb1f4fd7bc
parent 796b91dd4c63661b937b21b1576816086c86e573
Author: Jean Schurger <jean@schurger.org>
Date:   Wed,  4 Nov 2026 17:15:32 -0500

Choix des programmes dans une modale, avec la provenance du contenu

La liste en ligne (peu pratique) passe dans une boîte de dialogue ouverte par le bouton
« Choisir un programme… » : filtre, liste groupée titre + source (éditeur, année), clic
qui ferme et charge, bouton qui affiche le programme courant. Une note dit d'où vient le
contenu : pack TOSEC sur archive.org, ROM système MAME. Page dégagée (bouton + écran).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyJB9yqUAYm3wRQi4uytj9

Diffstat:
Mweb/shell.html.in | 56+++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 41 insertions(+), 15 deletions(-)

diff --git a/web/shell.html.in b/web/shell.html.in @@ -55,15 +55,24 @@ .toolbar input[type=file] { max-width: 230px; } .hidden { display: none !important; } - /* Liste des programmes (avec leur source), à la place d'un menu déroulant */ - .picker { max-width: 900px; margin: 0 auto 16px; } - .picker-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; - flex-wrap: wrap; margin: 0 0 6px; color: var(--muted); font-size: .9rem; } - .picker-head small { color: #7e7b74; } - #filter { background: #2b2d35; color: #fff; border: 1px solid #40434e; border-radius: 6px; - padding: 6px 10px; font: inherit; font-size: .9rem; min-width: 180px; } - .programs { max-height: 260px; overflow-y: auto; border: 1px solid #2a2c33; border-radius: 8px; - background: #17181c; } + /* Choix d'un programme : bouton + modale avec la liste (titre + source) */ + .pick-btn { background: var(--accent); color: #fff; border: 1px solid var(--accent); border-radius: 6px; + padding: 7px 14px; font: inherit; font-size: .95rem; cursor: pointer; font-weight: 600; } + .pick-btn:hover { filter: brightness(1.1); } + .modal { width: min(640px, 94vw); max-height: 82vh; padding: 0; border: 1px solid #343842; + border-radius: 12px; background: #17181c; color: var(--fg); overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,.6); } + .modal::backdrop { background: rgba(0,0,0,.6); } + .modal-head { display: flex; align-items: center; gap: 12px; padding: 12px 14px; + border-bottom: 1px solid #2a2c33; background: #1b1d22; } + .modal-head strong { font-size: 1.05rem; } + .modal-head #filter { flex: 1; background: #2b2d35; color: #fff; border: 1px solid #40434e; + border-radius: 6px; padding: 6px 10px; font: inherit; font-size: .9rem; min-width: 0; } + .modal-head .x { background: none; border: 0; color: var(--muted); font-size: 1.2rem; cursor: pointer; padding: 2px 6px; } + .modal-head .x:hover { color: #fff; } + .modal-src { margin: 0; padding: 9px 14px; border-top: 1px solid #2a2c33; background: #1b1d22; + color: var(--muted); font-size: .8rem; } + .programs { max-height: 60vh; overflow-y: auto; background: #17181c; } .pg-group { position: sticky; top: 0; background: #1b1d22; color: var(--cyan); font-size: .78rem; letter-spacing: .04em; text-transform: uppercase; padding: 6px 12px; border-bottom: 1px solid #2a2c33; } .pg { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; width: 100%; @@ -111,7 +120,9 @@ </label> <label class="hidden">Driver <select id="driver"><option value="">…</option></select></label> <label class="hidden">ROM <input id="rom" type="file"></label> - <label>Charger votre fichier + <button id="pick" type="button" class="pick-btn" title="Parcourir les cartouches et cassettes"> + 📂 Choisir un programme…</button> + <label>ou votre fichier <select id="mediatype"> <option value="--tape">cartouche / cassette</option> <option value="--cart">cartouche (--cart)</option> @@ -124,13 +135,17 @@ <details id="more" class="hidden"><summary>options</summary></details> </div> - <section class="picker"> - <div class="picker-head"> - <span>Programmes <small>cartouches et cassettes BASIC, avec leur source</small></span> + <dialog id="modal" class="modal"> + <div class="modal-head"> + <strong>Programmes</strong> <input id="filter" type="search" placeholder="Filtrer…" autocomplete="off"> + <button id="modal-close" type="button" class="x" aria-label="Fermer">✕</button> </div> <div id="programs" class="programs" role="listbox" aria-label="Programmes"></div> - </section> + <p class="modal-src">Contenu préservé par le pack <b>TOSEC</b> sur + <a href="https://archive.org/details/pack-roms-exelvision-exl-100">archive.org</a> ; chaque entrée + indique son éditeur et son année d'origine. ROM système : MAME.</p> + </dialog> <section class="screen"> <div class="bezel"><canvas id="canvas" tabindex="0" oncontextmenu="event.preventDefault()"></canvas></div> @@ -289,15 +304,26 @@ // Liste « Programmes » : cartouches et cassettes embarquées dans dsp.data, chacune avec sa source // (éditeur, année) lue dans titles.json. On relance l'EXL 100 avec le programme choisi (ou le logo). const programsEl = $('programs'); + const modal = $('modal'); + $('pick').onclick = () => { modal.showModal(); $('filter').focus(); }; + $('modal-close').onclick = () => modal.close(); + modal.addEventListener('click', (e) => { if (e.target === modal) modal.close(); }); // clic sur le fond ferme let bootToken = 0; // annule une frappe automatique en cours si on change de programme function markActive(value) { - for (const el of programsEl.querySelectorAll('.pg')) el.classList.toggle('active', el.dataset.value === value); + let title = ''; + for (const el of programsEl.querySelectorAll('.pg')) { + const on = el.dataset.value === value; + el.classList.toggle('active', on); + if (on && value) title = el.querySelector('.pg-t').textContent; + } + $('pick').textContent = title ? '📂 ' + title : '📂 Choisir un programme…'; } // Cartouche (cart:) = autonome. Cassette BASIC (tape:) = on charge ExelBASIC puis la cassette, // et la page tape elle-même la séquence de chargement de l'EXL 100 (LOAD"1", Échap, RUN). function bootProgram(value) { const token = ++bootToken; markActive(value); + if (modal.open) modal.close(); const args = ['--game', 'exl100']; let cassette = false; if (value.startsWith('cart:')) {