feat: Add visual scrubber to video editor

Task #69 completed by ClaudeQueue

ClaudeQueue
This commit is contained in:
Claude Worker
2026-07-18 17:50:27 +00:00
parent 8c0776f97d
commit 046aa67108
2 changed files with 56 additions and 0 deletions

View File

@@ -745,6 +745,11 @@ function openVideoEditor(source) {
body.className = 'video-editor';
body.innerHTML = `
<p class="ve-intro">Mark the parts to <strong>remove</strong>. Everything else is kept and saved as a new offline video.</p>
<div class="ve-scrubber-track">
<div class="ve-scrubber-bar"></div>
<div class="ve-marker ve-marker-a"></div>
<div class="ve-marker ve-marker-b"></div>
</div>
<div class="ve-add-row">
<label>From <input type="text" class="ve-from" placeholder="0:30" inputmode="numeric" /></label>
<label>To <input type="text" class="ve-to" placeholder="1:15" inputmode="numeric" /></label>
@@ -762,8 +767,33 @@ function openVideoEditor(source) {
const cutsEl = body.querySelector('.ve-cuts');
const titleEl = body.querySelector('.ve-title');
const sumEl = body.querySelector('.ve-summary');
const trackEl = body.querySelector('.ve-scrubber-track');
const markerA = body.querySelector('.ve-marker-a');
const markerB = body.querySelector('.ve-marker-b');
titleEl.value = (source.title || 'Video') + ' (edit)';
// Update markers based on duration
function updateScrubber() {
const a = VideoEdit.parseTime(fromEl.value);
const b = VideoEdit.parseTime(toEl.value);
if (a !== null) markerA.style.left = (a / duration * 100) + '%';
if (b !== null) markerB.style.left = (b / duration * 100) + '%';
}
trackEl.onclick = (e) => {
const rect = trackEl.getBoundingClientRect();
const percent = (e.clientX - rect.left) / rect.width;
const time = Math.round(percent * duration);
if (!fromEl.value) fromEl.value = VideoEdit.fmtTime(time);
else if (!toEl.value) toEl.value = VideoEdit.fmtTime(time);
updateScrubber();
};
fromEl.oninput = updateScrubber;
toEl.oninput = updateScrubber;
function showErr(msg) {
errEl.textContent = msg;
errEl.hidden = !msg;

View File

@@ -2259,3 +2259,29 @@ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); }
box-shadow: 0 2px 6px -2px var(--accent-glow);
}
.saved-card.custom .thumb { position: relative; }
/* Video Editor Scrubber */
.ve-scrubber-track {
height: 30px;
background: var(--bg-3);
border: 1px solid var(--line);
border-radius: var(--radius-sm);
position: relative;
cursor: pointer;
margin: 10px 0;
}
.ve-scrubber-bar {
height: 100%;
background: var(--bg-2);
border-radius: var(--radius-sm);
}
.ve-marker {
position: absolute;
top: 0;
width: 4px;
height: 100%;
background: var(--accent);
pointer-events: none;
}
.ve-marker-a { left: 0; }
.ve-marker-b { right: 0; }