feat: remove decorative day-timeline SVG, linear highlights panel

The user is blind; the day activity timeline (and its hour labels) was
purely visual, non-interactive, and carried no information not already
in the chips/summary. The highlights panel is now linear text and
buttons in reading order: summary line ("N files analysed - M loud
sections"), key-hint note (now always shown, J/K/U/I is the primary
interface), chips toggle, chips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 11:49:59 +02:00
parent 653084e90b
commit 41d921a42a
3 changed files with 14 additions and 102 deletions
+12 -100
View File
@@ -121,10 +121,6 @@ button.day-hl .day-arrow{font-size:9px}
h2.day-heading{margin:0;font-size:inherit;font-weight:inherit;line-height:inherit;flex:1 1 auto}
.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{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;
@@ -1016,78 +1012,7 @@ async function dayHighlights(dayId, analyzableFiles) {
return;
}
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';
const svg = document.createElementNS(ns, 'svg');
svg.setAttribute('class', 'wave day-timeline');
svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
svg.setAttribute('preserveAspectRatio', 'none');
svg.setAttribute('role', 'img');
const totalSecs = positioned.reduce((a, r) => a + r.data.sections.length, 0);
svg.setAttribute('aria-label', `Day activity: ${results.length} file${results.length!==1?'s':''}, ${totalSecs} loud section${totalSecs!==1?'s':''}`);
// Background track
const bgR = document.createElementNS(ns, 'rect');
bgR.setAttribute('x', 0); bgR.setAttribute('y', 7);
bgR.setAttribute('width', W); bgR.setAttribute('height', 8);
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);
// File span (dim blue)
const fr = document.createElementNS(ns, 'rect');
fr.setAttribute('x', fx); fr.setAttribute('y', 8);
fr.setAttribute('width', fw); fr.setAttribute('height', 6);
fr.setAttribute('fill', '#1e3a5f');
fr.setAttribute('aria-hidden', 'true');
svg.appendChild(fr);
// Loud sections (orange)
(data.sections || []).forEach(s => {
const sx = fx + (s.start / fileDur) * fw;
const sw = Math.max(1, ((s.end - s.start) / fileDur) * fw);
const sr = document.createElementNS(ns, 'rect');
sr.setAttribute('x', sx); sr.setAttribute('y', 4);
sr.setAttribute('width', sw); sr.setAttribute('height', 14);
sr.setAttribute('fill', '#f97316');
sr.setAttribute('rx', '1');
sr.setAttribute('aria-hidden', 'true');
svg.appendChild(sr);
});
});
const fmtHM = ts => {
const d = new Date(ts * 1000);
return d.getHours().toString().padStart(2,'0') + ':' + d.getMinutes().toString().padStart(2,'0');
};
// Build cross-file section list for J/K navigation and chips
dayActiveSections = [];
@@ -1112,23 +1037,21 @@ async function dayHighlights(dayId, analyzableFiles) {
const box = document.createElement('div');
box.className = 'wbox';
box.style.marginBottom = '4px';
box.appendChild(svg);
const labels = document.createElement('div');
labels.className = 'day-tl-labels';
// 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);
// Totals first, then the key hint, then the chips: linear reading order
const summary = document.createElement('div');
summary.className = 'quiet';
summary.style.marginTop = '0';
summary.textContent = `${results.length} file${results.length!==1?'s':''} analysed · ${totalSecs} loud section${totalSecs!==1?'s':''}`;
box.appendChild(summary);
if (dayActiveSections.length) {
const note = document.createElement('p');
note.className = 'quiet';
note.style.marginTop = '6px';
note.textContent = 'J / K plays through all sections in time order, U / I by loudness (loudest first)';
box.appendChild(note);
const MAX_DAY_CHIPS = 50;
// When there are too many sections to show them all, show the ones most
// worth reviewing: the top MAX_DAY_CHIPS by score, loudest first.
@@ -1138,11 +1061,6 @@ async function dayHighlights(dayId, analyzableFiles) {
chipList = chipList
.sort((a, b) => (b.sec.score || 0) - (a.sec.score || 0))
.slice(0, MAX_DAY_CHIPS);
const note = document.createElement('p');
note.className = 'quiet';
note.style.marginTop = '6px';
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';
@@ -1181,12 +1099,6 @@ async function dayHighlights(dayId, analyzableFiles) {
box.appendChild(chips);
}
const summary = document.createElement('div');
summary.className = 'quiet';
summary.style.marginTop = '4px';
summary.textContent = `${results.length} file${results.length!==1?'s':''} analysed · ${totalSecs} loud section${totalSecs!==1?'s':''}`;
box.appendChild(summary);
contentEl.innerHTML = '';
contentEl.appendChild(box);
hlRow.dataset.loaded = hlParams();