feat: cleaner day highlights panel

- Day timeline axis now labels round wall-clock hours with tick marks
  on the bar (start/end fallback when the span has <2 round hours),
  replacing the arbitrary start/midpoint/end labels.
- Long chip lists (>12) collapse behind an aria-expanded toggle button
  so the panel is not a wall of 50 buttons; J/K/U/I are unaffected.
- Chips group aria-labels shortened to "Day loud sections" / "Loud
  sections" - the key-binding explanation lives only in the visible
  note, not repeated in the group name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 11:39:00 +02:00
parent 98d2d7085d
commit 653084e90b
3 changed files with 61 additions and 9 deletions
+59 -7
View File
@@ -65,6 +65,7 @@ button.del:hover:not(:disabled){background:#2d0808}
.wbox{background:var(--surf);border:1px solid var(--brd);border-radius:6px;padding:10px 12px}
svg.wave{display:block;width:100%;height:56px}
.chips{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}
.chips[hidden]{display:none}
.chip{background:#431407;color:var(--orange);border:1px solid #7c2d12;border-radius:4px;
padding:2px 8px;font-size:11px;font-family:ui-monospace,monospace}
button.chip{cursor:pointer}
@@ -121,8 +122,9 @@ h2.day-heading{margin:0;font-size:inherit;font-weight:inherit;line-height:inheri
.day-hl-container{background:var(--bg);border:1px solid var(--brd);border-top:none;padding:8px 12px 12px}
table.day-table{width:100%;border-collapse:collapse;border:1px solid var(--brd);border-top:none}
svg.day-timeline{display:block;width:100%;height:22px}
.day-tl-labels{display:flex;justify-content:space-between;font-size:10px;
.day-tl-labels{position:relative;height:13px;font-size:10px;
color:var(--muted);font-family:ui-monospace,monospace;margin-top:2px}
.day-tl-labels span{position:absolute;top:0;white-space:nowrap}
/* clip player bar */
#clip-bar{position:fixed;bottom:0;left:0;right:0;z-index:20;background:var(--surf);
border-top:1px solid var(--brd);padding:8px 28px;display:flex;align-items:center;
@@ -539,7 +541,7 @@ async function analyse(idx, filename, cell, btn, force = false) {
const chips = document.createElement('div');
chips.className='chips';
chips.setAttribute('role','group');
chips.setAttribute('aria-label','Loud sections — click to jump, J/K to step in time order, U/I by loudness');
chips.setAttribute('aria-label','Loud sections');
if (d.sections && d.sections.length) {
sectionMap.set(idx, d.sections);
d.sections.forEach((s, si) => {
@@ -1017,6 +1019,17 @@ async function dayHighlights(dayId, analyzableFiles) {
const minT = Math.min(...positioned.map(r => r.fileStart));
const maxT = Math.max(...positioned.map(r => r.fileEnd));
const spanT = maxT - minT || 1;
// Axis ticks at round hours (aligned to the day, widest step that yields
// at most ~7 labels) instead of the arbitrary start/midpoint/end times
const stepH = [1, 2, 3, 6, 12].find(h => spanT / (h * 3600) <= 7) || 24;
const step = stepH * 3600;
const d0 = new Date(minT * 1000);
const dayStart = new Date(d0.getFullYear(), d0.getMonth(), d0.getDate()).getTime() / 1000;
const ticks = [];
for (let t = dayStart + Math.ceil((minT - dayStart) / step) * step; t <= maxT; t += step)
ticks.push(t);
const W = 800, H = 22;
const ns = 'http://www.w3.org/2000/svg';
@@ -1035,6 +1048,16 @@ async function dayHighlights(dayId, analyzableFiles) {
bgR.setAttribute('fill', '#1e2535');
svg.appendChild(bgR);
// Hour tick marks (drawn under the file spans and sections)
ticks.forEach(t => {
const tl = document.createElementNS(ns, 'rect');
tl.setAttribute('x', ((t - minT) / spanT) * W); tl.setAttribute('y', 4);
tl.setAttribute('width', 1); tl.setAttribute('height', 14);
tl.setAttribute('fill', '#2e3950');
tl.setAttribute('aria-hidden', 'true');
svg.appendChild(tl);
});
positioned.forEach(({ f, data, fileStart, fileEnd, fileDur }) => {
const fx = ((fileStart - minT) / spanT) * W;
const fw = Math.max(1, ((fileEnd - fileStart) / spanT) * W);
@@ -1093,9 +1116,16 @@ async function dayHighlights(dayId, analyzableFiles) {
const labels = document.createElement('div');
labels.className = 'day-tl-labels';
labels.innerHTML = `<span title="First recording starts">${esc(fmtHM(minT))}</span>`
+ `<span title="Timeline midpoint">${esc(fmtHM((minT+maxT)/2))}</span>`
+ `<span title="Last recording ends">${esc(fmtHM(maxT))}</span>`;
// Fewer than two round hours in the span: fall back to start/end times
(ticks.length >= 2 ? ticks : [minT, maxT]).forEach(t => {
const s = document.createElement('span');
s.textContent = fmtHM(t);
const pct = ((t - minT) / spanT) * 100;
if (pct < 4) s.style.left = '0';
else if (pct > 96) s.style.right = '0';
else { s.style.left = pct + '%'; s.style.transform = 'translateX(-50%)'; }
labels.appendChild(s);
});
box.appendChild(labels);
if (dayActiveSections.length) {
@@ -1111,13 +1141,13 @@ async function dayHighlights(dayId, analyzableFiles) {
const note = document.createElement('p');
note.className = 'quiet';
note.style.marginTop = '6px';
note.textContent = `${dayActiveSections.length} sections — chips show the top ${MAX_DAY_CHIPS} by loudness; J / K steps through all in time order, U / I by loudness (loudest first)`;
note.textContent = `${dayActiveSections.length} sections — J / K steps through all in time order, U / I by loudness (loudest first)`;
box.appendChild(note);
}
const chips = document.createElement('div');
chips.className = 'chips';
chips.setAttribute('role', 'group');
chips.setAttribute('aria-label', 'Day loud sections — click to jump, J/K to step across files in time order, U/I by loudness');
chips.setAttribute('aria-label', 'Day loud sections');
chipList.forEach(({sec, si}) => {
const c = document.createElement('button');
c.className = 'chip';
@@ -1126,6 +1156,28 @@ async function dayHighlights(dayId, analyzableFiles) {
c.addEventListener('click', () => jumpToDaySection(si));
chips.appendChild(c);
});
// A long chip list is a wall of buttons: collapse it behind a toggle
if (chipList.length > 12) {
chips.hidden = true;
const tog = document.createElement('button');
tog.style.marginTop = '8px';
tog.setAttribute('aria-expanded', 'false');
const arrow = document.createElement('span');
arrow.className = 'day-arrow';
arrow.setAttribute('aria-hidden', 'true');
arrow.textContent = '▸';
tog.appendChild(arrow);
tog.appendChild(document.createTextNode(truncated
? ` Top ${chipList.length} sections by loudness`
: ` ${chipList.length} sections`));
tog.addEventListener('click', () => {
const exp = chips.hidden;
chips.hidden = !exp;
tog.setAttribute('aria-expanded', exp);
arrow.textContent = exp ? '▾' : '▸';
});
box.appendChild(tog);
}
box.appendChild(chips);
}