Timers: show stopwatch + countdown in the BPM display; countdown blank-default, hⓂ️s input
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
72147f5b32
commit
285d78b499
1 changed files with 23 additions and 8 deletions
31
index.html
31
index.html
|
|
@ -55,6 +55,7 @@
|
|||
.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:40px; color:#ffd166; letter-spacing:3px; text-shadow:0 0 12px rgba(255,209,102,.5); }
|
||||
.display .dtimers { font-family:"Courier New",monospace; font-size:13px; color:#4dd0e1; margin:3px 0; display:flex; gap:14px; justify-content:center; flex-wrap:wrap; }
|
||||
.display .ctx { font-family:"Courier New",monospace; font-size:12px; color:#4dd0e1; min-height:15px; line-height:1.3; }
|
||||
.display .ctx.muted-cue { color:#ffb454; }
|
||||
.knob { margin-bottom:10px; }
|
||||
|
|
@ -183,6 +184,10 @@
|
|||
<div style="flex:0 0 190px; min-width:170px">
|
||||
<div class="display">
|
||||
<div class="big" id="bpmDisplay">120</div>
|
||||
<div class="dtimers">
|
||||
<span title="elapsed (stopwatch)">⏱ <span id="elapsedVal">0:00</span></span>
|
||||
<span id="countWrap" title="countdown" hidden>⏳ <span id="countVal" class="tval">0:00</span></span>
|
||||
</div>
|
||||
<div class="ctx" id="ctxDisplay"> </div>
|
||||
</div>
|
||||
<div class="btnrow" style="margin-top:10px"><button class="primary" id="startBtn">▶ Start</button><button id="tapBtn">Tap</button></div>
|
||||
|
|
@ -213,13 +218,11 @@
|
|||
</div>
|
||||
<div class="checkrow" style="margin:12px 0 6px; gap:8px"><b style="font-size:11px; text-transform:uppercase; letter-spacing:1px; color:var(--muted)">Timers</b><span class="hint" style="margin:0">run while playing</span></div>
|
||||
<div class="row" style="gap:10px; align-items:center">
|
||||
<label style="font-size:12px">Elapsed</label>
|
||||
<span class="tval" id="elapsedVal">0:00</span>
|
||||
<button class="iconbtn" id="elapsedReset" title="reset elapsed timer">⟲</button>
|
||||
<label style="font-size:12px">Elapsed (stopwatch)</label>
|
||||
<button class="iconbtn" id="elapsedReset" title="reset elapsed">⟲</button>
|
||||
</div>
|
||||
<div class="row" style="gap:10px; align-items:center; margin-top:6px">
|
||||
<label style="font-size:12px" title="0 = no countdown">Countdown <input type="number" class="num" id="countMin" min="0" max="120" value="5"> min</label>
|
||||
<span class="tval" id="countVal">5:00</span>
|
||||
<label style="font-size:12px">Countdown <input type="text" class="txt" id="countTime" placeholder="m:ss" title="blank = off · h:mm:ss, m:ss, or plain minutes" style="width:80px; text-align:center"></label>
|
||||
<button class="iconbtn" id="countReset" title="reset countdown">⟲</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -951,8 +954,18 @@ function drawLoop() {
|
|||
/* =========================================================================
|
||||
PRACTICE TIMERS — advance only while the metronome is running
|
||||
========================================================================= */
|
||||
const timers = { elapsedMs: 0, totalMs: 5 * 60000, remainingMs: 5 * 60000, last: 0 };
|
||||
const timers = { elapsedMs: 0, totalMs: 0, remainingMs: 0, last: 0 }; // countdown off by default
|
||||
function fmtClock(ms) { const neg = ms < 0; const s = Math.round(Math.abs(ms) / 1000); return (neg ? "-" : "") + Math.floor(s / 60) + ":" + String(s % 60).padStart(2, "0"); }
|
||||
// Parse a countdown duration: blank = off; "h:mm:ss" / "m:ss" (seconds-last); a plain number = minutes.
|
||||
function parseTime(str) {
|
||||
str = (str || "").trim(); if (!str) return 0;
|
||||
if (!str.includes(":")) { const m = parseFloat(str); return isFinite(m) && m > 0 ? Math.round(m * 60000) : 0; }
|
||||
const p = str.split(":").map((x) => parseInt(x, 10) || 0);
|
||||
let h = 0, m = 0, s = 0;
|
||||
if (p.length >= 3) { h = p[0]; m = p[1]; s = p[2]; } else { m = p[0]; s = p[1]; }
|
||||
const ms = ((h * 60 + m) * 60 + s) * 1000;
|
||||
return ms > 0 ? ms : 0;
|
||||
}
|
||||
function tickTimers() {
|
||||
const now = Date.now();
|
||||
const dt = timers.last ? Math.min(now - timers.last, 1000) : 0; // clamp so backgrounded gaps don't jump
|
||||
|
|
@ -965,8 +978,10 @@ function tickTimers() {
|
|||
}
|
||||
function renderTimers() {
|
||||
$("elapsedVal").textContent = fmtClock(timers.elapsedMs);
|
||||
const off = timers.totalMs <= 0;
|
||||
$("countWrap").hidden = off; // hide countdown when off
|
||||
if (off) return;
|
||||
const cd = $("countVal");
|
||||
if (timers.totalMs <= 0) { cd.textContent = "off"; cd.className = "tval"; return; }
|
||||
cd.textContent = fmtClock(timers.remainingMs);
|
||||
cd.classList.toggle("over", timers.remainingMs <= 0); // overtime
|
||||
cd.classList.toggle("low", timers.remainingMs > 0 && timers.remainingMs <= 10000); // almost up
|
||||
|
|
@ -1037,7 +1052,7 @@ $("rampStart").addEventListener("input", (e) => ramp.startBpm = +e.target.value)
|
|||
$("rampAmt").addEventListener("input", (e) => ramp.amount = +e.target.value);
|
||||
$("rampEvery").addEventListener("input", (e) => ramp.everyBars = +e.target.value);
|
||||
$("addMeterBtn").addEventListener("click", () => addMeter("4", 1, "claves"));
|
||||
$("countMin").addEventListener("input", (e) => { timers.totalMs = (+e.target.value || 0) * 60000; timers.remainingMs = timers.totalMs; renderTimers(); });
|
||||
$("countTime").addEventListener("input", (e) => { timers.totalMs = parseTime(e.target.value); timers.remainingMs = timers.totalMs; renderTimers(); });
|
||||
$("elapsedReset").addEventListener("click", () => { timers.elapsedMs = 0; renderTimers(); });
|
||||
$("countReset").addEventListener("click", () => { timers.remainingMs = timers.totalMs; renderTimers(); });
|
||||
$("trayMenuBtn").addEventListener("click", (e) => { e.stopPropagation(); $("trayMenu").hidden = !$("trayMenu").hidden; });
|
||||
|
|
|
|||
Loading…
Reference in a new issue