diff --git a/index.html b/index.html index 32a9be7..56f2028 100644 --- a/index.html +++ b/index.html @@ -144,7 +144,7 @@ input,textarea{font:inherit;color:inherit} #bar::before{content:"";position:absolute;left:0;right:0;top:-14px;bottom:-14px} /* a fat finger target around a thin line */ #bar i{display:block;height:100%;width:0;background:var(--acc);border-radius:2px;pointer-events:none} #bar:hover,#bar.drag{height:4px;margin:-1px 0} -#bar em{position:absolute;top:-16px;transform:translateX(-50%);font-size:11px;font-style:normal;line-height:1;pointer-events:none;opacity:.9} +#bar em{position:absolute;top:-18px;transform:translateX(-50%);font-size:11px;font-style:normal;line-height:1;pointer-events:none;opacity:.95;white-space:nowrap;background:var(--card);border:1px solid var(--line);border-radius:999px;padding:2px 5px} #bar b{position:absolute;top:-28px;transform:translateX(-50%);font:11px var(--font);letter-spacing:.1em;color:var(--dim);background:var(--card);border:1px solid var(--line);border-radius:6px;padding:2px 6px;pointer-events:none;display:none;white-space:nowrap} #bar.drag b{display:block} #controls{display:flex;align-items:center;gap:18px} @@ -1196,7 +1196,7 @@ function toCooking(job) { if (!job.stepping) { state.history.push(spec); state.played = (state.played || 0) + 1; spec.stationId ||= state.stationId; } keepLabel(); for (const t of $("#bar").querySelectorAll("em")) t.remove(); - if (spec.hits) { const dur = spec.seconds || 120; for (const h of spec.hits) placeTick($("#bar"), h.e, h.t, dur); } + drawTicks(spec, spec.seconds || 120); $("#title").textContent = spec.break ? "✱ " + spec.title : spec.title; $("#tags").textContent = `${spec.bpm} bpm · ${spec.key}${spec.break ? " · " + (spec.sponsored ? "sponsored" : "intermission") : spec.remixOf ? " · remix" : ""}`; $("#lyric").textContent = spec.line || ""; showTags(spec); @@ -1410,15 +1410,19 @@ const mmss = (s) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart function markReaction(spec, emoji, t) { const dur = state.audio.duration || spec.seconds || 120; spec.hits = (spec.hits || []).concat([{ e: emoji, t: Math.round(t), s: sectionAt(spec, t) }]).slice(-24); - const bar = $("#bar"); if (!bar || state.current.spec !== spec) return; - placeTick(bar, emoji, t, dur); + if (state.current.spec === spec) drawTicks(spec, dur); } -/* a tick on the bar; ones within a second of each other stack upward so none hides another */ -function placeTick(bar, emoji, t, dur) { - const tick = document.createElement("em"); tick.textContent = emoji; tick.title = `${emoji} at ${mmss(t)}`; - const left = Math.min(100, t / dur * 100); - const near = [...bar.querySelectorAll("em")].filter((x) => Math.abs(parseFloat(x.dataset.t) - t) < 1.5).length; - tick.dataset.t = t; tick.style.left = left + "%"; tick.style.top = (-16 - near * 13) + "px"; bar.append(tick); +/* the ticks above the bar: hits within a few percent of each other become one pill reading them in order */ +function drawTicks(spec, dur) { + const bar = $("#bar"); if (!bar) return; + for (const t of bar.querySelectorAll("em")) t.remove(); + const hits = (spec.hits || []).slice().sort((a, b) => a.t - b.t), groups = []; + for (const h of hits) { const g = groups[groups.length - 1]; if (g && h.t - g.at <= dur * 0.04) { g.e.push(h.e); g.last = h.t; } else groups.push({ at: h.t, last: h.t, e: [h.e] }); } + for (const g of groups) { + const tick = document.createElement("em"); const shown = g.e.slice(0, 3).join("") + (g.e.length > 3 ? "+" + (g.e.length - 3) : ""); + tick.textContent = shown; tick.title = `${g.e.join(" ")} at ${mmss(g.at)}${g.last !== g.at ? "–" + mmss(g.last) : ""}`; + tick.style.left = Math.min(100, (g.at + g.last) / 2 / dur * 100) + "%"; bar.append(tick); + } } async function react(emoji, text) { const cur = state.current && state.current.spec; if (!cur) return;