From 863e55e3aa8aa593ba9c2aa58d4edfba04fc90e1 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 29 Jul 2026 14:25:00 +0300 Subject: [PATCH 1/2] feat: add 'slip into the tail' feature simple implementaion of sliding the clip being edited within in/out points (only to the left) --- CHANGELOG.md | 4 ++++ CLAUDE.md | 9 +++++++++ README.md | 2 +- app.js | 35 +++++++++++++++++++++++++++++++++++ index.html | 2 ++ 5 files changed, 51 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 803246a..9743e5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **Slide** (timeline toolbar) — with a video/audio clip selected and the + playhead over it, slips the source `in`/`out` window so the frame under the + playhead becomes the new in-point; timeline start and duration stay fixed. + Linked AV partners stay in sync. Toasts when there isn’t enough media tail. - Preview playback speed — a monitor toolbar toggle plus **J**/**K**/**L** shortcuts cycle the preview player through 1×, 1.5×, 2×, and 4× (L faster, J slower, K play/pause and reset to 1×). It rides on top of each clip's own speed and is forced back to 1× during export, so renders always come out at real time. ## [1.6.0] - 2026-07-14 diff --git a/CLAUDE.md b/CLAUDE.md index 4065cc2..305ae9b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -345,6 +345,10 @@ glitch (RGB split + jitter) · pop (overshoot scale — stickers/captions). `transitionIn: {type:"fade"}`. - A cut/split is just two clips: first with `duration: t`, second with `start: +t, in: +t×speed, duration: rest`. +- A **slide** (UI toolbar) keeps `start`/`duration` and sets `in` to the media + time under the playhead (source out moves by the same delta). Requires + `in + sourceWindow ≤ media.duration` afterward; linked AV partners share the + new `in` via the usual timing sync. - `bgRemove` and `chromaKey` can combine with all filters; heavy pixel work is automatic (only runs when those props are set). @@ -425,6 +429,11 @@ of previous durations. **Music bed**: audio file on A1, `props.volume: 0.3`, trim with `in`/`duration`. +**Slide (slip) source**: with the playhead over a video/audio clip, raise `in` +so the frame under the playhead becomes the clip in-point; leave `start` and +`duration` unchanged. The UI **Slide** button does this and toasts if the +source has no unused tail (`in + duration×speed` would exceed `media.duration`). + **Cinematic grade**: `props.filterPreset: "teal-orange"` (tweak with `temperature`/`vignette` on top). diff --git a/README.md b/README.md index a31d19e..7b85b2b 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ same time. **Editing** -- 3 video tracks + 4 audio tracks, drag/trim/split/snap, undo/redo +- 3 video tracks + 4 audio tracks, drag/trim/split/slide/snap, undo/redo - **Settings** (cog in the top bar) — optional prefs stored in this browser via `localStorage`. Enable **Link timeline and Project bin selection** so picking a timeline clip highlights its media in Project, and clicking a Project item diff --git a/app.js b/app.js index b412674..0f41311 100644 --- a/app.js +++ b/app.js @@ -1866,6 +1866,40 @@ function trimToPlayhead(side) { syncLinkedTiming(c); scheduleSave(); renderInspector(); } +/* Slip the selected clip’s source window: keep timeline start/duration, + set `in` to the media time under the playhead (source out moves by the + same delta). Needs enough media after the current out (“tail”). */ +function slideClipAtPlayhead() { + const c = getClip(state.selId); + if (!c) { toast("Select a clip first"); return; } + if (c.kind !== "video" && c.kind !== "audio") { + toast("Slide works on video or audio clips"); + return; + } + const t = state.time; + if (t < c.start || t >= clipEnd(c)) { toast("Move playhead over the selected clip"); return; } + const media = getMedia(c.mediaId); + if (!media || !(Number(media.duration) > 0)) { + toast("Media duration unknown — wait for probe"); + return; + } + // mediaTimeAt already honors static speed and speed-ramp integrals + const srcIn = mediaTimeAt(c, t); + const srcUsed = mediaTimeAt(c, clipEnd(c)) - c.in; + if (!(srcIn - c.in > 1e-4)) { + toast("Playhead is already at the clip in-point"); + return; + } + if (srcIn + srcUsed > media.duration + 1e-4) { + toast("Not enough media tail to slide"); + return; + } + pushUndo(); + c.in = srcIn; + syncLinkedTiming(c); + scheduleSave(); renderInspector(); + seekMediaWhilePaused(); +} /* Split at IN/OUT and discard clip heads before IN and tails after OUT. Skips disabled tracks when track enable/disable is available. */ function trimToWorkArea() { @@ -5441,6 +5475,7 @@ els.fileInput.addEventListener("change", () => { importFiles(els.fileInput.files $("btnTitle").addEventListener("click", addTitle); $("btnAdjust").addEventListener("click", addAdjust); $("btnSplit").addEventListener("click", splitAtPlayhead); +$("btnSlide").addEventListener("click", slideClipAtPlayhead); $("btnCloseGap").addEventListener("click", closeGapAtPlayhead); $("btnNextGap").addEventListener("click", goToNextGap); $("btnTrimIO").addEventListener("click", trimToWorkArea); diff --git a/index.html b/index.html index bfbd1b1..c2494ef 100644 --- a/index.html +++ b/index.html @@ -124,6 +124,7 @@ + @@ -207,6 +208,7 @@

Keyboard shortcuts

SpacePlay / pause J K LShuttle preview — L faster / J slower (1× · 1.5× · 2× · 4×); K play/pause + reset to 1× SSplit clip(s) at playhead + Slide (toolbar)Slip selected clip source so playhead frame becomes the in-point GFind next shared gap (wraps; respects IN/OUT) +GClose gap at playhead (enabled tracks) Alt+TAdd transition at playhead (in / out by half) From 8c02d6814f72a17b7e7db6452b18125d87d4f557 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 29 Jul 2026 15:50:38 +0300 Subject: [PATCH 2/2] docs: Clarify the 'slide' feature in documentation --- CLAUDE.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 305ae9b..301cf39 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -431,8 +431,10 @@ of previous durations. **Slide (slip) source**: with the playhead over a video/audio clip, raise `in` so the frame under the playhead becomes the clip in-point; leave `start` and -`duration` unchanged. The UI **Slide** button does this and toasts if the -source has no unused tail (`in + duration×speed` would exceed `media.duration`). +`duration` unchanged. The UI **Slide** button does this and toasts if there is +no unused source tail — i.e. if the new `in` plus the integrated source window +(`mediaTimeAt(clipEnd) − in`, which covers static speed and speed ramps) would +exceed `media.duration`. **Cinematic grade**: `props.filterPreset: "teal-orange"` (tweak with `temperature`/`vignette` on top).