From bfc2649909268855d1690814bd7ace1645443975 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Tue, 19 May 2026 23:20:10 -0400 Subject: [PATCH] feat(editor): fps-aware render, FPS selector in new-seq dialog, keyboard help overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - openSequence() and applyHistory() now pass state.seq.frame_rate to Timeline.render() instead of hardcoded 59.94 — clips render on the correct frame grid for every sequence - New-sequence panel gains a frame-rate selector (23.976 / 24 / 25 / 29.97 / 30 / 50 / 59.94 / 60); createNewSequence() posts frame_rate to the API - Press ? to open a keyboard shortcut help overlay; Escape to close --- services/web-ui/public/editor.html | 159 ++++++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 3 deletions(-) diff --git a/services/web-ui/public/editor.html b/services/web-ui/public/editor.html index 8f3e129..ce601d1 100644 --- a/services/web-ui/public/editor.html +++ b/services/web-ui/public/editor.html @@ -219,6 +219,85 @@ transition: background var(--t-fast); } .topbar-seq-name:hover { background: var(--bg-hover); } + + /* ── Keyboard shortcut help overlay ───────────────────────── */ + .kbd-help-backdrop { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.5); + z-index: 900; + } + .kbd-help-backdrop.open { display: block; } + + .kbd-help-panel { + display: none; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: var(--bg-panel); + border: 1px solid var(--border); + border-radius: var(--r-lg); + box-shadow: 0 24px 64px rgba(0,0,0,0.45); + width: 480px; + max-width: 90vw; + z-index: 901; + flex-direction: column; + } + .kbd-help-panel.open { display: flex; } + + .kbd-help-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--sp-4) var(--sp-5); + border-bottom: 1px solid var(--border); + font-weight: 600; + font-size: var(--text-sm); + color: var(--text-primary); + } + + .kbd-help-body { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--sp-5); + padding: var(--sp-5); + } + + .kbd-section-title { + font-size: var(--text-xs); + font-weight: 500; + text-transform: uppercase; + letter-spacing: .08em; + color: var(--text-tertiary); + margin-bottom: var(--sp-3); + } + + .kbd-row { + display: flex; + align-items: center; + gap: var(--sp-3); + margin-bottom: var(--sp-2); + font-size: var(--text-sm); + color: var(--text-secondary); + } + + kbd { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 2px 7px; + background: var(--bg-raised); + border: 1px solid var(--border-strong, var(--border)); + border-radius: var(--r-sm); + font-family: var(--font-mono, monospace); + font-size: 11px; + color: var(--text-primary); + white-space: nowrap; + min-width: 24px; + flex-shrink: 0; + } @@ -358,6 +437,8 @@
+
+
@@ -384,6 +465,19 @@ +
+ + +
+ +
+ + @@ -514,7 +641,7 @@ async function openSequence(id) { state.history = [cloneClips(state.seq.clips)]; state.historyIdx = 0; document.getElementById('seqSelect').value = id; - Timeline.render(state.seq.clips, { fps: 59.94 }); + Timeline.render(state.seq.clips, { fps: state.seq.frame_rate || 59.94 }); updateProgramScrub(); } @@ -865,7 +992,7 @@ function redo() { function applyHistory() { const clips = cloneClips(state.history[state.historyIdx]); state.seq.clips = clips; - Timeline.render(clips, { fps: 59.94 }); + Timeline.render(clips, { fps: state.seq.frame_rate || 59.94 }); markDirty(); } @@ -891,6 +1018,7 @@ function setupToolbar() { document.getElementById('undoBtn').onclick = undo; document.getElementById('redoBtn').onclick = redo; document.getElementById('saveBtn').onclick = saveSequence; + document.getElementById('helpBtn').onclick = openKbdHelp; document.getElementById('exportEdlBtn').onclick = function() { if (!state.seq) { toast('No sequence open', '', 'warning'); return; } exportSequenceEDL(state.seq.id, (state.seq.name || 'sequence') + '.edl'); @@ -902,6 +1030,9 @@ function setupKeyboard() { const tag = document.activeElement.tagName; if (['INPUT', 'TEXTAREA', 'SELECT'].includes(tag)) return; + if (e.key === 'Escape') { closeKbdHelp(); return; } + if (e.key === '?') { e.preventDefault(); toggleKbdHelp(); return; } + if (e.code === 'Space') { e.preventDefault(); togglePgmPlay(); return; } if (e.key === 'j' || e.key === 'J') { stopPgm(); return; } if (e.key === 'k' || e.key === 'K') { stopPgm(); return; } @@ -929,6 +1060,27 @@ function updateToolbarActive(tool) { Timeline.setTool(tool); } +// ════════════════════════════════════════════════════════════════ +// KEYBOARD HELP OVERLAY +// ════════════════════════════════════════════════════════════════ +function openKbdHelp() { + document.getElementById('kbdHelpPanel').classList.add('open'); + document.getElementById('kbdHelpBackdrop').classList.add('open'); +} +function closeKbdHelp() { + document.getElementById('kbdHelpPanel').classList.remove('open'); + document.getElementById('kbdHelpBackdrop').classList.remove('open'); +} +function toggleKbdHelp() { + if (document.getElementById('kbdHelpPanel').classList.contains('open')) closeKbdHelp(); + else openKbdHelp(); +} + +document.addEventListener('DOMContentLoaded', function() { + document.getElementById('closeKbdHelp').onclick = closeKbdHelp; + document.getElementById('kbdHelpBackdrop').onclick = closeKbdHelp; +}); + // ════════════════════════════════════════════════════════════════ // NEW SEQUENCE PANEL // ════════════════════════════════════════════════════════════════ @@ -942,7 +1094,8 @@ function setupNewSeqPanel() { async function createNewSequence() { const name = document.getElementById('newSeqName').value.trim() || 'Sequence ' + (state.sequences.length + 1); - const r = await createSequence({ project_id: state.projectId, name: name }); + const fps = parseFloat(document.getElementById('newSeqFps').value) || 59.94; + const r = await createSequence({ project_id: state.projectId, name: name, frame_rate: fps }); if (!r.success) { toast('Failed to create sequence', r.error, 'error'); return; } closePanel('seq'); document.getElementById('newSeqName').value = '';