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