@@ -120,14 +145,8 @@ export function ExportDialog({
)}
{showSuccess && (
-
diff --git a/src/components/video-editor/PlaybackControls.tsx b/src/components/video-editor/PlaybackControls.tsx
index 24370a1..f567235 100644
--- a/src/components/video-editor/PlaybackControls.tsx
+++ b/src/components/video-editor/PlaybackControls.tsx
@@ -1,5 +1,6 @@
import { Button } from "../ui/button";
-import { MdPlayArrow, MdPause } from "react-icons/md";
+import { Play, Pause } from "lucide-react";
+import { cn } from "@/lib/utils";
interface PlaybackControlsProps {
isPlaying: boolean;
@@ -27,36 +28,63 @@ export default function PlaybackControls({
onSeek(parseFloat(e.target.value));
}
+ const progress = duration > 0 ? (currentTime / duration) * 100 : 0;
+
return (
-
+
-
+
+
{formatTime(currentTime)}
-
-
+
+
+ {/* Custom Track Background */}
+
+
+ {/* Interactive Input */}
+
+
+ {/* Custom Thumb (visual only, follows progress) */}
+
+
+
+
{formatTime(duration)}
diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx
index 53e1dd0..9bdf7c8 100644
--- a/src/components/video-editor/SettingsPanel.tsx
+++ b/src/components/video-editor/SettingsPanel.tsx
@@ -11,7 +11,7 @@ import { Trash2, Download, Crop, X, Bug } from "lucide-react";
import type { ZoomDepth, CropRegion } from "./types";
import { CropControl } from "./CropControl";
-const WALLPAPER_COUNT = 12;
+const WALLPAPER_COUNT = 23;
const WALLPAPER_RELATIVE = Array.from({ length: WALLPAPER_COUNT }, (_, i) => `wallpapers/wallpaper${i + 1}.jpg`);
const GRADIENTS = [
"linear-gradient( 111.6deg, rgba(114,167,232,1) 9.4%, rgba(253,129,82,1) 43.9%, rgba(253,129,82,1) 54.8%, rgba(249,202,86,1) 86.3% )",
@@ -26,6 +26,19 @@ const GRADIENTS = [
"linear-gradient(109.6deg, #F635A6, #36D860)",
"linear-gradient(90deg, #FF0101, #4DFF01)",
"linear-gradient(315deg, #EC0101, #5044A9)",
+ // New Gradients
+ "linear-gradient(45deg, #ff9a9e 0%, #fad0c4 99%, #fad0c4 100%)",
+ "linear-gradient(to top, #a18cd1 0%, #fbc2eb 100%)",
+ "linear-gradient(to right, #ff8177 0%, #ff867a 0%, #ff8c7f 21%, #f99185 52%, #cf556c 78%, #b12a5b 100%)",
+ "linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%)",
+ "linear-gradient(to right, #4facfe 0%, #00f2fe 100%)",
+ "linear-gradient(to top, #fcc5e4 0%, #fda34b 15%, #ff7882 35%, #c8699e 52%, #7046aa 71%, #0c1db8 87%, #020f75 100%)",
+ "linear-gradient(to right, #fa709a 0%, #fee140 100%)",
+ "linear-gradient(to top, #30cfd0 0%, #330867 100%)",
+ "linear-gradient(to top, #c471f5 0%, #fa71cd 100%)",
+ "linear-gradient(to right, #f78ca0 0%, #f9748f 19%, #fd868c 60%, #fe9a8b 100%)",
+ "linear-gradient(to top, #48c6ef 0%, #6f86d6 100%)",
+ "linear-gradient(to right, #0acffe 0%, #495aff 100%)",
];
interface SettingsPanelProps {
@@ -83,13 +96,13 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
};
return (
-
-
-
-
Zoom Level
+
+
+
+ Zoom Level
{zoomEnabled && selectedZoomDepth && (
-
- Active · {ZOOM_DEPTH_OPTIONS.find(o => o.depth === selectedZoomDepth)?.label}
+
+ {ZOOM_DEPTH_OPTIONS.find(o => o.depth === selectedZoomDepth)?.label} Active
)}
@@ -103,74 +116,75 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
disabled={!zoomEnabled}
onClick={() => onZoomDepthChange?.(option.depth)}
className={cn(
- "h-auto w-full rounded-xl border px-2 py-3 text-center shadow-lg transition-all flex flex-col items-center justify-center gap-1",
- "duration-150 ease-in-out",
- zoomEnabled ? "opacity-100" : "opacity-60",
+ "h-auto w-full rounded-xl border px-1 py-3 text-center shadow-sm transition-all flex flex-col items-center justify-center gap-1.5",
+ "duration-200 ease-out",
+ zoomEnabled ? "opacity-100 cursor-pointer" : "opacity-40 cursor-not-allowed",
isActive
- ? "border-[#34B27B] bg-white text-black shadow-[#34B27B]/20 scale-105"
- : "border-[#23232a] bg-[#23232a] text-slate-200 hover:border-[#34B27B] hover:scale-105"
+ ? "border-[#34B27B] bg-[#34B27B] text-white shadow-[#34B27B]/20 scale-105 ring-2 ring-[#34B27B]/20"
+ : "border-white/5 bg-white/5 text-slate-400 hover:bg-white/10 hover:border-white/10 hover:text-slate-200"
)}
- style={isActive ? { background: '#fff', color: '#111' } : undefined}
>
-
{option.label}
+
{option.label}
);
})}
{!zoomEnabled && (
-
Select a zoom item in the timeline to adjust its depth.
+
Select a zoom region in the timeline to adjust depth.
)}
{zoomEnabled && (
)}
-
-
-
-
+
+
-
+
+
-
- If the preview looks weirdly positioned at any time, try force reloading
+
+ If the preview looks weirdly positioned at any time, try force reloading (you will lose all your progress)
{showCropDropdown && cropRegion && onCropChange && (
<>
setShowCropDropdown(false)}
/>
-
+
Crop Video
@@ -180,7 +194,7 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
variant="ghost"
size="icon"
onClick={() => setShowCropDropdown(false)}
- className="hover:bg-[#34B27B]/20 text-slate-200"
+ className="hover:bg-white/10 text-slate-400 hover:text-white"
>
@@ -194,6 +208,7 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
@@ -201,100 +216,107 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
>
)}
-
-
- Image
- Color
- Gradient
+
+
+
+ Image
+ Color
+ Gradient
-
-
- {(wallpaperPaths.length > 0 ? wallpaperPaths : WALLPAPER_RELATIVE.map(p => `/${p}`)).map((path, idx) => {
- const isSelected = (() => {
- if (!selected) return false;
-
- if (selected === path) return true;
- try {
- const clean = (s: string) => s.replace(/^file:\/\//, '').replace(/^\//, '')
- if (clean(selected).endsWith(clean(path))) return true;
- if (clean(path).endsWith(clean(selected))) return true;
- } catch {
- }
- return false;
- })();
+
+
+
+ {(wallpaperPaths.length > 0 ? wallpaperPaths : WALLPAPER_RELATIVE.map(p => `/${p}`)).map((path, idx) => {
+ const isSelected = (() => {
+ if (!selected) return false;
+
+ if (selected === path) return true;
+ try {
+ const clean = (s: string) => s.replace(/^file:\/\//, '').replace(/^\//, '')
+ if (clean(selected).endsWith(clean(path))) return true;
+ if (clean(path).endsWith(clean(selected))) return true;
+ } catch {
+ }
+ return false;
+ })();
- return (
+ return (
+
onWallpaperChange(path)}
+ role="button"
+ />
+ )
+ })}
+
+
+
+
+
+ {
+ setHsva(color.hsva);
+ onWallpaperChange(hsvaToHex(color.hsva));
+ }}
+ style={{ width: '100%', borderRadius: '12px' }}
+ />
+
+
+
+
+
+ {GRADIENTS.map((g, idx) => (
onWallpaperChange(path)}
+ style={{ background: g }}
+ aria-label={`Gradient ${idx + 1}`}
+ onClick={() => { setGradient(g); onWallpaperChange(g); }}
role="button"
/>
- )
- })}
-
-
-
-
-
- {
- setHsva(color.hsva);
- onWallpaperChange(hsvaToHex(color.hsva));
- }}
- />
-
-
-
-
-
- {GRADIENTS.map((g, idx) => (
-
{ setGradient(g); onWallpaperChange(g); }}
- role="button"
- />
- ))}
-
-
-
-
-
-
+ ))}
+
+
+
+
+
+
+
+
);
}
diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx
index 3c7e3e8..58b6911 100644
--- a/src/components/video-editor/VideoEditor.tsx
+++ b/src/components/video-editor/VideoEditor.tsx
@@ -1,6 +1,6 @@
-import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
import { Toaster } from "@/components/ui/sonner";
import { toast } from "sonner";
@@ -22,7 +22,7 @@ import {
} from "./types";
import { VideoExporter, type ExportProgress } from "@/lib/exporter";
-const WALLPAPER_COUNT = 12;
+const WALLPAPER_COUNT = 23;
const WALLPAPER_PATHS = Array.from({ length: WALLPAPER_COUNT }, (_, i) => `/wallpapers/wallpaper${i + 1}.jpg`);
export default function VideoEditor() {
@@ -153,10 +153,7 @@ export default function VideoEditor() {
}
}, [selectedZoomId]);
- const selectedZoom = useMemo(() => {
- if (!selectedZoomId) return null;
- return zoomRegions.find((region) => region.id === selectedZoomId) ?? null;
- }, [selectedZoomId, zoomRegions]);
+
useEffect(() => {
if (selectedZoomId && !zoomRegions.some((region) => region.id === selectedZoomId)) {
@@ -271,35 +268,26 @@ export default function VideoEditor() {
const isMac = navigator.userAgent.includes('Mac');
return (
-
+
{/* Drag region for window - more padding on macOS for traffic lights */}
-
-
-
-
-
setShowExportDialog(false)}
- progress={exportProgress}
- isExporting={isExporting}
- error={exportError}
- onCancel={handleCancelExport}
- />
-
-
- {videoPath && (
- <>
-
+
+
+ {/* Left Column - Video & Timeline */}
+
+ {/* Video Preview Area */}
+
+
+
-
- >
- )}
+
+ {/* Floating Playback Controls */}
+
+
+
+
+ {/* Timeline Area */}
+
+
+
-
z.id === selectedZoomId)?.depth : null}
+ onZoomDepthChange={(depth) => selectedZoomId && handleZoomDepthChange(depth)}
selectedZoomId={selectedZoomId}
- onSelectZoom={handleSelectZoom}
+ onZoomDelete={handleZoomDelete}
+ showShadow={showShadow}
+ onShadowChange={setShowShadow}
+ showBlur={showBlur}
+ onBlurChange={setShowBlur}
+ cropRegion={cropRegion}
+ onCropChange={setCropRegion}
+ videoElement={videoPlaybackRef.current?.video || null}
+ onExport={handleExport}
/>
-
+
+
setShowExportDialog(false)}
+ progress={exportProgress}
+ isExporting={isExporting}
+ error={exportError}
+ onCancel={handleCancelExport}
/>
-
);
}
\ No newline at end of file
diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx
index 4396086..8c43ca3 100644
--- a/src/components/video-editor/VideoPlayback.tsx
+++ b/src/components/video-editor/VideoPlayback.tsx
@@ -741,7 +741,7 @@ const VideoPlayback = forwardRef
(({
>
diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx
index 150061d..190546b 100644
--- a/src/components/video-editor/timeline/TimelineEditor.tsx
+++ b/src/components/video-editor/timeline/TimelineEditor.tsx
@@ -3,6 +3,7 @@ import { useTimelineContext } from "dnd-timeline";
import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react";
import { toast } from "sonner";
+import { cn } from "@/lib/utils";
import TimelineWrapper from "./TimelineWrapper";
import Row from "./Row";
import Item from "./Item";
@@ -151,41 +152,21 @@ function PlaybackCursor({
);
@@ -241,7 +222,7 @@ function TimelineAxis({
return (
-
-
+
+
{marker.label}
@@ -298,7 +263,7 @@ function Timeline({
items,
videoDurationMs,
intervalMs,
- currentTimeMs,
+ currentTimeMs,
onSeek,
onSelectZoom,
selectedZoomId,
@@ -333,10 +298,11 @@ function Timeline({
-
+
+
{items.map((item) => (
@@ -468,33 +434,37 @@ export default function TimelineEditor({
if (!videoDuration || videoDuration === 0) {
return (
-
-
Load a video to see timeline
+
+ Load a video to see timeline
);
}
return (
-
-
-