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:
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:')) {