From fc0a801e8829b623d62137a3f7cbdec38c6d555a Mon Sep 17 00:00:00 2001 From: cashmere Date: Sun, 26 Jul 2026 21:06:33 +0200 Subject: feat: freeze frame at playhead Enable the timeline toolbar freeze-frame button: capture the composited canvas as PNG, add it as an image media asset, split the video element at the playhead, shift the right part by the freeze duration, and insert a 3-second image element in the gap. Adds RendererManager.captureFrame(), reused by createSnapshot(). Also adds the Nix devshell (flake.nix/flake.lock: bun + nodejs) and AGENTS.md repository guidelines. --- apps/web/src/timeline/components/timeline-toolbar.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'apps/web/src/timeline') diff --git a/apps/web/src/timeline/components/timeline-toolbar.tsx b/apps/web/src/timeline/components/timeline-toolbar.tsx index 22acd447..6208ef27 100644 --- a/apps/web/src/timeline/components/timeline-toolbar.tsx +++ b/apps/web/src/timeline/components/timeline-toolbar.tsx @@ -95,6 +95,18 @@ function ToolbarLeftSection() { const isCurrentlyBookmarked = useEditor((e) => e.scenes.isBookmarked({ time: e.playback.getCurrentTime() }), ); + const canFreezeFrame = useEditor((e) => { + const time = e.playback.getCurrentTime(); + const { overlay, main } = e.scenes.getActiveScene().tracks; + return [...overlay, main].some((track) => + track.elements.some( + (element) => + element.type === "video" && + time > element.startTime && + time < element.startTime + element.duration, + ), + ); + }); const selectedElement = selectedElements.length === 1 ? (editor.timeline.getElementsWithTracks({ @@ -185,9 +197,9 @@ function ToolbarLeftSection() { } - tooltip="Freeze frame (coming soon)" - disabled={true} - onClick={({ event: _event }) => {}} + tooltip="Freeze frame" + disabled={!canFreezeFrame} + onClick={({ event }) => handleAction({ action: "freeze-frame", event })} />