From 6433f3a599da46ef8794e007342bd96cfd2a92a5 Mon Sep 17 00:00:00 2001 From: kunal_yelgate Date: Tue, 16 Jun 2026 16:08:30 +0530 Subject: [PATCH 1/3] Add animation to the export button --- src/components/VideoEditor.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/VideoEditor.tsx b/src/components/VideoEditor.tsx index a12c1f41..6ab7ed48 100644 --- a/src/components/VideoEditor.tsx +++ b/src/components/VideoEditor.tsx @@ -313,6 +313,7 @@ return () => { }, [file]); const isProcessing = status === "loading-engine" || status === "exporting"; + const isReadyToExport = Boolean(file) && status === "idle"; const isMac = typeof navigator !== "undefined" && /Mac/i.test(navigator.platform); const intervalSeconds = useMemo(() => { @@ -741,12 +742,13 @@ return () => { className={cn( "w-full flex items-center justify-center gap-3 py-5 min-h-[44px] rounded-xl", "font-display text-2xl tracking-widest transition-all duration-200", + isReadyToExport && "motion-safe:animate-pulse motion-reduce:animate-none", file && !isProcessing ? "bg-[var(--accent)] hover:bg-[var(--accent-hover)] hover:scale-[1.02] text-white shadow-[var(--shadow)] active:scale-[0.98] cursor-pointer" : "bg-[var(--border)] text-[var(--muted)] cursor-not-allowed" )} > - + {isProcessing ? "PROCESSING" : "EXPORT"} From bbf205a1a7fb531374e2449f5a306a0803a4947a Mon Sep 17 00:00:00 2001 From: kunal_yelgate Date: Tue, 16 Jun 2026 16:37:57 +0530 Subject: [PATCH 2/3] Add pulse animation --- src/components/VideoEditor.tsx | 10 +++++--- src/lib/exportButtonAnimation.ts | 17 +++++++++++++ src/lib/tests/exportButtonAnimation.test.ts | 27 +++++++++++++++++++++ tailwind.config.ts | 11 +++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/lib/exportButtonAnimation.ts create mode 100644 src/lib/tests/exportButtonAnimation.test.ts diff --git a/src/components/VideoEditor.tsx b/src/components/VideoEditor.tsx index 6ab7ed48..c1f1ab32 100644 --- a/src/components/VideoEditor.tsx +++ b/src/components/VideoEditor.tsx @@ -27,6 +27,10 @@ import { import OnboardingTour from "./OnboardingTour"; import { useKeyboardShortcuts } from "@/hooks/useKeyboardShortcuts"; import { loadOverlayState, persistOverlayState } from "@/lib/editorPersistence"; +import { + getExportButtonAnimationClass, + isReadyToExport, +} from "@/lib/exportButtonAnimation"; interface SectionProps { icon: React.ReactNode; @@ -313,7 +317,7 @@ return () => { }, [file]); const isProcessing = status === "loading-engine" || status === "exporting"; - const isReadyToExport = Boolean(file) && status === "idle"; + const isReadyToExportState = isReadyToExport(Boolean(file), status); const isMac = typeof navigator !== "undefined" && /Mac/i.test(navigator.platform); const intervalSeconds = useMemo(() => { @@ -742,7 +746,7 @@ return () => { className={cn( "w-full flex items-center justify-center gap-3 py-5 min-h-[44px] rounded-xl", "font-display text-2xl tracking-widest transition-all duration-200", - isReadyToExport && "motion-safe:animate-pulse motion-reduce:animate-none", + getExportButtonAnimationClass(Boolean(file), status), file && !isProcessing ? "bg-[var(--accent)] hover:bg-[var(--accent-hover)] hover:scale-[1.02] text-white shadow-[var(--shadow)] active:scale-[0.98] cursor-pointer" : "bg-[var(--border)] text-[var(--muted)] cursor-not-allowed" @@ -752,7 +756,7 @@ return () => { {isProcessing ? "PROCESSING" : "EXPORT"} - {file && !isProcessing && ( + {isReadyToExportState && (

{isMac ? "⌘" : "Ctrl"} + Enter to export

diff --git a/src/lib/exportButtonAnimation.ts b/src/lib/exportButtonAnimation.ts new file mode 100644 index 00000000..b91b483c --- /dev/null +++ b/src/lib/exportButtonAnimation.ts @@ -0,0 +1,17 @@ +import { ExportStatus } from "@/lib/types"; + +export const READY_EXPORT_BUTTON_ANIMATION_CLASS = + "motion-safe:animate-export-ready-pulse motion-reduce:animate-none"; + +export function isReadyToExport(hasFile: boolean, status: ExportStatus): boolean { + return hasFile && status === "idle"; +} + +export function getExportButtonAnimationClass( + hasFile: boolean, + status: ExportStatus +): string { + return isReadyToExport(hasFile, status) + ? READY_EXPORT_BUTTON_ANIMATION_CLASS + : ""; +} diff --git a/src/lib/tests/exportButtonAnimation.test.ts b/src/lib/tests/exportButtonAnimation.test.ts new file mode 100644 index 00000000..c29ff583 --- /dev/null +++ b/src/lib/tests/exportButtonAnimation.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from "vitest"; + +import { + getExportButtonAnimationClass, + isReadyToExport, + READY_EXPORT_BUTTON_ANIMATION_CLASS, +} from "../exportButtonAnimation"; + +describe("exportButtonAnimation", () => { + it("marks export as ready only when a file is loaded and status is idle", () => { + expect(isReadyToExport(true, "idle")).toBe(true); + expect(isReadyToExport(false, "idle")).toBe(false); + expect(isReadyToExport(true, "loading-engine")).toBe(false); + expect(isReadyToExport(true, "exporting")).toBe(false); + expect(isReadyToExport(true, "done")).toBe(false); + expect(isReadyToExport(true, "error")).toBe(false); + }); + + it("returns the reduced-motion-safe animation class only in the ready state", () => { + expect(getExportButtonAnimationClass(true, "idle")).toBe( + READY_EXPORT_BUTTON_ANIMATION_CLASS + ); + expect(getExportButtonAnimationClass(false, "idle")).toBe(""); + expect(getExportButtonAnimationClass(true, "exporting")).toBe(""); + expect(getExportButtonAnimationClass(true, "done")).toBe(""); + }); +}); diff --git a/tailwind.config.ts b/tailwind.config.ts index c79da9a2..5dca82da 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -34,9 +34,20 @@ const config: Config = { shimmer: { "100%": { transform: "translateX(100%)" }, }, + exportReadyPulse: { + "0%, 100%": { + boxShadow: "var(--shadow)", + filter: "brightness(1)", + }, + "50%": { + boxShadow: "0 0 0 3px var(--accent-muted), var(--shadow)", + filter: "brightness(1.03)", + }, + }, }, animation: { shimmer: "shimmer 2s infinite", + "export-ready-pulse": "exportReadyPulse 2.4s ease-in-out infinite", }, }, }, From 60c786715ea8c141907fb38c424e4cea78f3a7e6 Mon Sep 17 00:00:00 2001 From: kunal_yelgate Date: Tue, 16 Jun 2026 17:22:02 +0530 Subject: [PATCH 3/3] Add pulse animation --- .github/workflows/claude-manager.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/claude-manager.yml b/.github/workflows/claude-manager.yml index 07fef221..8c8e81be 100644 --- a/.github/workflows/claude-manager.yml +++ b/.github/workflows/claude-manager.yml @@ -35,7 +35,6 @@ jobs: with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} - allowed_non_write_users: "*" trigger_phrase: "" direct_prompt: | You are the automated manager for magic-peach/reframe, a GSSoC'26 open-source project. @@ -75,7 +74,6 @@ jobs: with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} - allowed_non_write_users: "*" trigger_phrase: "" direct_prompt: | You are the automated manager for magic-peach/reframe, a GSSoC'26 open-source project.