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:
+59
-7
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user