Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/claude-manager.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 8 additions & 2 deletions src/components/VideoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -313,6 +317,7 @@ return () => {
}, [file]);

const isProcessing = status === "loading-engine" || status === "exporting";
const isReadyToExportState = isReadyToExport(Boolean(file), status);
const isMac = typeof navigator !== "undefined" && /Mac/i.test(navigator.platform);

const intervalSeconds = useMemo(() => {
Expand Down Expand Up @@ -741,16 +746,17 @@ 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",
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"
)}
>
<Zap size={20} className={cn(file && !isProcessing && "animate-pulse")} />
<Zap size={20} />
{isProcessing ? "PROCESSING" : "EXPORT"}
</button>

{file && !isProcessing && (
{isReadyToExportState && (
<p className="text-xs text-center font-mono text-[var(--muted)] opacity-50 mt-1">
{isMac ? "⌘" : "Ctrl"} + Enter to export
</p>
Expand Down
17 changes: 17 additions & 0 deletions src/lib/exportButtonAnimation.ts
Original file line number Diff line number Diff line change
@@ -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
: "";
}
27 changes: 27 additions & 0 deletions src/lib/tests/exportButtonAnimation.test.ts
Original file line number Diff line number Diff line change
@@ -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("");
});
});
11 changes: 11 additions & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
},
},
Expand Down