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 @@
+
+
+
+
+
+
+
+
+
+
+
Playback
+
SpacePlay / Pause
+
JStop
+
LPlay
+
In / Out
+
IMark In
+
OMark Out
+
+
+
Tools
+
VSelect
+
CRazor
+
HHand
+
Edit
+
Ctrl+ZUndo
+
Ctrl+⇧+ZRedo
+
Ctrl+SSave
+
?This help
+
+
+
+
@@ -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 = '';