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;