From 17492fdfb00563882d2cb99aef673d0d61918570 Mon Sep 17 00:00:00 2001 From: Me Here Date: Mon, 25 May 2026 07:45:26 -0500 Subject: [PATCH] Single Save button by Tap (disabled when nothing loaded); per-entry + Clear-all history delete; bigger display text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move per-item 💾 out of set-list rows into one 💾 Save next to Tap; it overwrites the loaded item and is disabled when no item is loaded. - History list: red ✕ on hover deletes one session; Clear all wipes history for the current item only. - Bump dark-display text again (BPM 80px, timers 26px, status 19px); widen the display column to fit. - README: play key Space -> P. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 2 +- index.html | 67 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 43bbabb..a8feec4 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ In the set‑list panel's **⋯** menu: | Key | Action | |-----|--------| -| `Space` | start / stop | +| `P` | play / stop | | `T` | tap tempo | | `↑` / `↓` | tempo ±1 (`Shift` = ±10) | | `A` | add meter lane | diff --git a/index.html b/index.html index ce014b8..af9c366 100644 --- a/index.html +++ b/index.html @@ -54,10 +54,10 @@ .card { background:var(--panel-2); border:1px solid var(--edge); border-radius:12px; padding:13px; flex:1; min-width:240px; } .card h2 { font-size:11px; text-transform:uppercase; letter-spacing:1.4px; color:var(--muted); margin:0 0 14px; } .display { background:#0a0d11; border:1px solid #000; border-radius:8px; padding:8px 14px; text-align:center; box-shadow:inset 0 2px 10px rgba(0,0,0,.7); } - .display .big { font-family:"Courier New",monospace; font-weight:700; font-size:58px; color:#ffd166; letter-spacing:2px; line-height:1.05; text-shadow:0 0 12px rgba(255,209,102,.5); } - .display .dtimers { font-family:"Courier New",monospace; font-size:18px; color:#4dd0e1; margin:4px 0; display:flex; gap:16px; justify-content:center; flex-wrap:wrap; } + .display .big { font-family:"Courier New",monospace; font-weight:700; font-size:80px; color:#ffd166; letter-spacing:2px; line-height:1.0; text-shadow:0 0 12px rgba(255,209,102,.5); } + .display .dtimers { font-family:"Courier New",monospace; font-size:26px; color:#4dd0e1; margin:6px 0; display:flex; gap:18px; justify-content:center; flex-wrap:wrap; } .display .dtimers[hidden] { display:none; } - .display .ctx { font-family:"Courier New",monospace; font-size:15px; color:#4dd0e1; min-height:18px; line-height:1.3; } + .display .ctx { font-family:"Courier New",monospace; font-size:19px; color:#4dd0e1; min-height:22px; line-height:1.25; } .display .ctx.muted-cue { color:#ffb454; } .knob { margin-bottom:10px; } .knob label { display:flex; justify-content:space-between; font-size:12px; margin-bottom:5px; } @@ -114,8 +114,13 @@ .np-desc { font-size:12px; color:var(--muted); margin-top:4px; } .iconbtn { padding:3px 8px; font-size:12px; } .log-item { padding:8px 10px; border:1px solid var(--edge); border-radius:8px; margin-bottom:6px; background:var(--panel); } - .log-head { font-weight:600; font-size:13px; margin-bottom:3px; } - .log-seg { font-size:12px; color:var(--muted); margin:2px 0 0 12px; font-family:"Courier New",monospace; } + .log-head { font-weight:600; font-size:13px; margin-bottom:5px; display:flex; align-items:center; gap:8px; } + .log-head-nm { flex:1; } + .hist-row { display:flex; align-items:center; gap:6px; margin:2px 0 0 12px; } + .hist-txt { flex:1; font-size:12px; color:var(--muted); font-family:"Courier New",monospace; } + .hist-del { display:none; background:transparent; border:none; color:#ff6b5e; cursor:pointer; font-size:12px; line-height:1; padding:2px 4px; border-radius:4px; } + .hist-del:hover { background:rgba(255,107,94,.15); } + .hist-row:hover .hist-del, .hist-row:focus-within .hist-del { display:inline; } .practice { border-top:1px solid var(--edge); margin-top:16px; padding-top:4px; } /* set-list panel: always shown — sticky beside the metronome on desktop, stacks below it on narrow screens */ @@ -190,7 +195,7 @@
-
+
120
@@ -199,7 +204,7 @@
 
-
+
@@ -756,6 +761,7 @@ function toggleTransport() { // --- now-playing info on the main screen (replaces the old preset dropdown) --- function renderNowPlaying() { const sl = getSL(); const it = (sl && activeItem >= 0) ? sl.items[activeItem] : null; + $("saveItemBtn").disabled = !it; // single save button targets the loaded item if (!it) { $("npName").textContent = "Free play"; $("npSub").textContent = "No set-list item loaded — edit the lanes freely."; @@ -790,11 +796,9 @@ function renderItems() { row.innerHTML = `${i + 1}. ${it.name} ${it.bpm} · ${it.lanes.map((l) => l.groupsStr).join("/")} - `; row.onclick = () => loadItem(i); - row.querySelector('[data-act=save]').onclick = (e) => { e.stopPropagation(); updateItem(i); }; row.querySelector('[data-act=del]').onclick = (e) => { e.stopPropagation(); removeItem(i); }; box.appendChild(row); }); @@ -813,11 +817,41 @@ function logFinalize() { function renderLog() { const box = $("logView"); box.innerHTML = ""; if (!historyName) { box.innerHTML = '
Play a set-list item to see its history — compare BPM & duration across days.
'; return; } - const logs = lsGet(LS.logs, []).filter((e) => e.name === historyName); - let html = `
History — ${historyName}
`; - if (!logs.length) html += '
No past sessions for this item yet.
'; - else html += logs.map((e) => `
${new Date(e.at).toLocaleString()} · ${fmtDur(e.durationSec)} @ ${e.bpm}bpm
`).join(""); - box.innerHTML = html; + const entries = lsGet(LS.logs, []).filter((e) => e.name === historyName); + + const head = document.createElement("div"); head.className = "log-head"; + head.innerHTML = `History — ${historyName}`; + if (entries.length) { + const clr = document.createElement("button"); clr.className = "iconbtn"; clr.textContent = "Clear all"; + clr.title = "delete all history for this item"; + clr.onclick = () => clearItemHistory(); + head.appendChild(clr); + } + box.appendChild(head); + + if (!entries.length) { + const h = document.createElement("div"); h.className = "hint"; h.textContent = "No past sessions for this item yet."; + box.appendChild(h); return; + } + entries.forEach((e) => { + const row = document.createElement("div"); row.className = "hist-row"; + const txt = document.createElement("span"); txt.className = "hist-txt"; + txt.textContent = `${new Date(e.at).toLocaleString()} · ${fmtDur(e.durationSec)} @ ${e.bpm}bpm`; + const del = document.createElement("button"); del.className = "hist-del"; del.textContent = "✕"; + del.title = "delete this entry"; + del.onclick = () => deleteHistoryEntry(e.at); + row.appendChild(txt); row.appendChild(del); box.appendChild(row); + }); +} +function deleteHistoryEntry(at) { // remove one session by its timestamp + const logs = lsGet(LS.logs, []).filter((e) => !(e.at === at && e.name === historyName)); + lsSet(LS.logs, logs); renderLog(); +} +function clearItemHistory() { // clear every session for the current item + if (!historyName) return; + if (!confirm("Clear all history for “" + historyName + "”? (other items, set lists & presets are kept)")) return; + const logs = lsGet(LS.logs, []).filter((e) => e.name !== historyName); + lsSet(LS.logs, logs); renderLog(); } function clearLog() { if (confirm("Clear the practice log? (set lists & presets are kept)")) { lsSet(LS.logs, []); renderLog(); } } function resetAll() { @@ -1078,6 +1112,11 @@ function tapTempo() { } } $("tapBtn").addEventListener("click", tapTempo); +$("saveItemBtn").addEventListener("click", () => { + if (activeItem < 0) return; + updateItem(activeItem); + const b = $("saveItemBtn"), t = b.textContent; b.textContent = "✓ Saved"; setTimeout(() => { b.textContent = t; }, 900); +}); $("bpm").addEventListener("input", (e) => setBpm(+e.target.value)); $("vol").addEventListener("input", (e) => { state.volume = +e.target.value / 100; volVal.textContent = e.target.value + "%";