ui updates
This commit is contained in:
@@ -9,7 +9,8 @@ const Switch = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"data-[state=checked]:bg-[#7c3aed] data-[state=unchecked]:bg-[#23232a]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -17,7 +18,9 @@ const Switch = React.forwardRef<
|
||||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
|
||||
"pointer-events-none block h-4 w-4 rounded-full shadow-lg ring-0 transition-transform",
|
||||
"bg-[#f5f5f7] dark:bg-[#23232a]",
|
||||
"data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
|
||||
@@ -28,11 +28,11 @@ export default function PlaybackControls({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-4 px-4">
|
||||
<div className="flex items-center gap-4 px-4 rounded-xl py-3">
|
||||
<Button
|
||||
onClick={onTogglePlayPause}
|
||||
size="icon"
|
||||
className="w-8 h-8 rounded-full bg-primary text-primary-foreground hover:bg-primary/90 transition-colors shadow-md"
|
||||
className="w-8 h-8 rounded-full bg-transparent text-slate-200 hover:bg-[#18181b] transition-colors border border-white"
|
||||
aria-label={isPlaying ? 'Pause' : 'Play'}
|
||||
>
|
||||
{isPlaying ? (
|
||||
@@ -41,7 +41,7 @@ export default function PlaybackControls({
|
||||
<MdPlayArrow width={18} height={18} />
|
||||
)}
|
||||
</Button>
|
||||
<span className="text-xs text-muted-foreground font-mono">
|
||||
<span className="text-xs text-slate-400 font-mono">
|
||||
{formatTime(currentTime)}
|
||||
</span>
|
||||
<input
|
||||
@@ -51,12 +51,12 @@ export default function PlaybackControls({
|
||||
value={currentTime}
|
||||
onChange={handleSeekChange}
|
||||
step="0.01"
|
||||
className="flex-1 h-2 accent-blue-500 rounded-full transition-all duration-[33ms]"
|
||||
className="flex-1 h-2 rounded-full transition-all duration-[33ms] custom-playback-range"
|
||||
style={{
|
||||
background: `linear-gradient(to right, #3b82f6 0%, #3b82f6 ${(currentTime / duration) * 100}%, #e5e7eb ${(currentTime / duration) * 100}%, #e5e7eb 100%)`
|
||||
}}
|
||||
background: `linear-gradient(to right, #7c3aed 0%, #7c3aed ${(currentTime / duration) * 100}%, #23232a ${(currentTime / duration) * 100}%, #23232a 100%)`,
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground font-mono">
|
||||
<span className="text-xs text-slate-400 font-mono">
|
||||
{formatTime(duration)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -64,12 +64,12 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-[3] min-w-0 bg-card border border-border rounded-xl p-8 flex flex-col shadow-sm">
|
||||
<div className="flex-[3] min-w-0 bg-[#18181b] border border-[#23232a] rounded-xl p-8 flex flex-col shadow-lg">
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-sm font-semibold text-slate-600">Zoom Level</span>
|
||||
<span className="text-sm font-semibold text-slate-200">Zoom Level</span>
|
||||
{zoomEnabled && selectedZoomDepth && (
|
||||
<span className="text-xs uppercase tracking-wide text-slate-400">
|
||||
<span className="text-xs uppercase tracking-wide text-slate-400/80">
|
||||
Active · {ZOOM_DEPTH_OPTIONS.find(o => o.depth === selectedZoomDepth)?.label}
|
||||
</span>
|
||||
)}
|
||||
@@ -81,32 +81,32 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
|
||||
<Button
|
||||
key={option.depth}
|
||||
type="button"
|
||||
variant="outline"
|
||||
disabled={!zoomEnabled}
|
||||
onClick={() => onZoomDepthChange?.(option.depth)}
|
||||
className={cn(
|
||||
"h-auto w-full rounded-lg border bg-muted/30 px-2 py-2.5 text-center shadow-sm transition-all",
|
||||
"flex flex-col items-center justify-center gap-0.5",
|
||||
"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",
|
||||
isActive
|
||||
? "border-primary/70 bg-primary/10 text-primary shadow-primary/20"
|
||||
: "border-border/60 hover:border-primary/40 hover:bg-muted/60"
|
||||
? "border-[#7c3aed] bg-white text-black shadow-[#7c3aed]/20 scale-105"
|
||||
: "border-[#23232a] bg-[#23232a] text-slate-200 hover:border-[#7c3aed] hover:scale-105"
|
||||
)}
|
||||
style={isActive ? { background: '#fff', color: '#111' } : undefined}
|
||||
>
|
||||
<span className="text-xs font-semibold tracking-tight">{option.label}</span>
|
||||
<span className={cn("text-sm font-semibold tracking-tight", isActive ? "text-black" : "text-slate-200")}>{option.label}</span>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{!zoomEnabled && (
|
||||
<p className="text-xs text-slate-400 mt-2">Select a zoom item in the timeline to adjust its depth.</p>
|
||||
<p className="text-xs text-slate-400/80 mt-2">Select a zoom item in the timeline to adjust its depth.</p>
|
||||
)}
|
||||
{zoomEnabled && (
|
||||
<Button
|
||||
onClick={handleDeleteClick}
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="mt-3 w-full gap-2"
|
||||
className="mt-3 w-full gap-2 bg-[#7c3aed] text-white border-none hover:bg-[#a78bfa]"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
Delete Zoom
|
||||
@@ -120,14 +120,14 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
|
||||
checked={showShadow}
|
||||
onCheckedChange={onShadowChange}
|
||||
/>
|
||||
<div className="text-sm">Shadow</div>
|
||||
<div className="text-sm text-slate-200">Shadow</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={showBlur}
|
||||
onCheckedChange={onBlurChange}
|
||||
/>
|
||||
<div className="text-sm">Blur Background</div>
|
||||
<div className="text-sm text-slate-200">Blur Background</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,7 +135,7 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
|
||||
<Button
|
||||
onClick={() => setShowCropDropdown(!showCropDropdown)}
|
||||
variant="outline"
|
||||
className="w-full gap-2"
|
||||
className="w-full gap-2 bg-[#23232a] text-slate-200 border-none"
|
||||
>
|
||||
<Crop className="w-4 h-4" />
|
||||
Crop Video
|
||||
@@ -145,20 +145,20 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
|
||||
{showCropDropdown && cropRegion && onCropChange && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 bg-black/70 backdrop-blur-sm z-40 animate-in fade-in duration-200"
|
||||
className="fixed inset-0 bg-black/80 backdrop-blur-sm z-40 animate-in fade-in duration-200"
|
||||
onClick={() => setShowCropDropdown(false)}
|
||||
/>
|
||||
<div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-50 bg-card rounded-2xl shadow-2xl border border-border/50 p-8 w-[90vw] max-w-5xl animate-in zoom-in-95 duration-200">
|
||||
<div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-50 bg-[#23232a] rounded-2xl shadow-2xl border border-[#312e81] p-8 w-[90vw] max-w-5xl animate-in zoom-in-95 duration-200">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<span className="text-xl font-bold text-foreground">Crop Video</span>
|
||||
<p className="text-sm text-muted-foreground mt-2">Drag on each side to adjust the crop area</p>
|
||||
<span className="text-xl font-bold text-slate-200">Crop Video</span>
|
||||
<p className="text-sm text-slate-400 mt-2">Drag on each side to adjust the crop area</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setShowCropDropdown(false)}
|
||||
className="hover:bg-muted"
|
||||
className="hover:bg-[#312e81] text-slate-200"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</Button>
|
||||
@@ -179,11 +179,11 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Tabs defaultValue="image" className="mb-6">
|
||||
<TabsList className="mb-4">
|
||||
<TabsTrigger value="image">Image</TabsTrigger>
|
||||
<TabsTrigger value="color">Color</TabsTrigger>
|
||||
<TabsTrigger value="gradient">Gradient</TabsTrigger>
|
||||
<Tabs defaultValue="image" className="mb-6 text-slate-200">
|
||||
<TabsList className="mb-4 bg-[#23232a] border-none text-slate-200">
|
||||
<TabsTrigger value="image" className="text-slate-200">Image</TabsTrigger>
|
||||
<TabsTrigger value="color" className="text-slate-200">Color</TabsTrigger>
|
||||
<TabsTrigger value="gradient" className="text-slate-200">Gradient</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="image">
|
||||
@@ -194,8 +194,8 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
|
||||
className={cn(
|
||||
"aspect-square rounded-lg border-2 overflow-hidden cursor-pointer transition-all w-16 h-16",
|
||||
selected === path
|
||||
? "border-primary/40 ring-1 ring-primary/40 scale-105"
|
||||
: "border-border hover:border-primary/60 hover:scale-105"
|
||||
? "border-[#7c3aed] ring-1 ring-[#7c3aed] scale-105"
|
||||
: "border-[#23232a] hover:border-[#7c3aed] hover:scale-105"
|
||||
)}
|
||||
style={{ backgroundImage: `url(${path})`, backgroundSize: "cover", backgroundPosition: "center" }}
|
||||
aria-label={`Wallpaper ${idx + 1}`}
|
||||
@@ -207,14 +207,16 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="color">
|
||||
<Colorful
|
||||
color={hsva}
|
||||
disableAlpha={true}
|
||||
onChange={(color) => {
|
||||
setHsva(color.hsva);
|
||||
onWallpaperChange(hsvaToHex(color.hsva));
|
||||
}}
|
||||
/>
|
||||
<div className="p-2">
|
||||
<Colorful
|
||||
color={hsva}
|
||||
disableAlpha={true}
|
||||
onChange={(color) => {
|
||||
setHsva(color.hsva);
|
||||
onWallpaperChange(hsvaToHex(color.hsva));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="gradient">
|
||||
@@ -224,7 +226,7 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
|
||||
key={g}
|
||||
className={cn(
|
||||
"aspect-square rounded-lg border-2 overflow-hidden cursor-pointer transition-all w-16 h-16",
|
||||
gradient === g ? "border-primary ring-1 ring-primary/40 scale-105" : "border-border hover:border-primary/60 hover:scale-105"
|
||||
gradient === g ? "border-[#7c3aed] ring-1 ring-[#7c3aed] scale-105" : "border-[#23232a] hover:border-[#7c3aed] hover:scale-105"
|
||||
)}
|
||||
style={{ background: g }}
|
||||
aria-label={`Gradient ${idx + 1}`}
|
||||
@@ -239,7 +241,7 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
|
||||
<Button
|
||||
type="button"
|
||||
size="lg"
|
||||
className="w-full py-5 text-lg flex items-center justify-center gap-3 bg-primary text-white rounded-xl shadow-lg hover:bg-primary/90 transition-all"
|
||||
className="w-full py-5 text-lg flex items-center justify-center gap-3 bg-[#7c3aed] text-white rounded-xl shadow-lg hover:bg-[#a78bfa] transition-all"
|
||||
>
|
||||
<Download className="w-6 h-6" />
|
||||
<span className="text-lg">Export Video</span>
|
||||
@@ -249,9 +251,9 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
|
||||
onClick={() => {
|
||||
window.electronAPI?.openExternalUrl('https://github.com/siddharthvaddem/pangolin/issues/new');
|
||||
}}
|
||||
className="w-full mt-3 flex items-center justify-center gap-1 text-[10px] text-muted-foreground/60 hover:text-muted-foreground/90 transition-colors py-1"
|
||||
className="w-full mt-3 flex items-center justify-center gap-1 text-[10px] text-slate-400/80 hover:text-slate-200 transition-colors py-1"
|
||||
>
|
||||
<Bug className="w-3 h-3 text-black" />
|
||||
<Bug className="w-3 h-3 text-white" />
|
||||
<span>Report Bug</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -171,10 +171,10 @@ export default function VideoEditor() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-background p-8 gap-8">
|
||||
<div className="flex h-screen bg-background bg-black p-8 gap-8">
|
||||
<Toaster position="top-center" />
|
||||
<div className="flex flex-col flex-[7] min-w-0 gap-8">
|
||||
<div className="flex flex-col gap-6 flex-1">
|
||||
<div className="flex flex-col flex-[7] min-w-0 gap-6">
|
||||
<div className="flex flex-col gap-3 flex-1">
|
||||
{videoPath && (
|
||||
<>
|
||||
<div className="flex justify-center w-full">
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
.itemDark {
|
||||
background: #23232a;
|
||||
border: 1px solid #312e81;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.18);
|
||||
color: #e5e7eb;
|
||||
transition: box-shadow 0.2s, border 0.2s, background 0.2s;
|
||||
}
|
||||
.squircle {
|
||||
border-radius: 12px;
|
||||
-corner-smoothing: antialiased;
|
||||
|
||||
@@ -26,6 +26,7 @@ export default function Item({ id, span, rowId, isSelected = false, onSelect }:
|
||||
{...listeners}
|
||||
{...attributes}
|
||||
onPointerDownCapture={() => onSelect?.()}
|
||||
className={cn(glassStyles.itemDark)}
|
||||
>
|
||||
<div style={itemContentStyle}>
|
||||
<div
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
-corner-smoothing: antialiased;
|
||||
background: radial-gradient(circle at 60% 55%, rgba(104, 61, 196, 0.92) 60%, rgba(60, 20, 120, 0.85) 100%);
|
||||
background: #7c3aed;
|
||||
border: none;
|
||||
box-shadow: 0 2px 8px 0 rgba(88,36,204,0.10) inset, 0 1px 3px 0 rgba(0,0,0,0.10);
|
||||
box-shadow: 0 2px 12px 0 rgba(88,36,204,0.14) inset, 0 2px 8px 0 rgba(0,0,0,0.10), 0 1px 6px 0 rgba(124,58,237,0.08);
|
||||
margin: 0 4px;
|
||||
backdrop-filter: blur(2px) saturate(120%);
|
||||
-webkit-backdrop-filter: blur(2px) saturate(120%);
|
||||
}
|
||||
@@ -12,13 +13,13 @@
|
||||
.zoomEndCap {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
background: #3c3c3c;
|
||||
width: 18px;
|
||||
height: 100%;
|
||||
background: #361e5a;
|
||||
|
||||
width: 18px;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
transition: background 0.2s, box-shadow 0.2s;
|
||||
|
||||
}
|
||||
|
||||
.zoomEndCap.left {
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function Row({ id, children }: RowProps) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="border-b border-slate-100 bg-gradient-to-b from-slate-50/30 to-white/50"
|
||||
className="border-b border-[#18181b] bg-[#18181b]"
|
||||
style={{ ...rowWrapperStyle, minHeight: 88 }}
|
||||
>
|
||||
<div ref={setNodeRef} style={rowStyle}>
|
||||
|
||||
@@ -4,7 +4,7 @@ interface SubrowProps {
|
||||
|
||||
export default function Subrow({ children }: SubrowProps) {
|
||||
return (
|
||||
<div style={{ height: 50, position: "relative" }}>
|
||||
<div className={cn("flex items-center min-h-[32px] gap-1 px-2 py-0.5 bg-[#23232a] rounded-md text-slate-300")}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -151,27 +151,42 @@ function PlaybackCursor({
|
||||
<div
|
||||
className="absolute top-0 bottom-0 pointer-events-none z-50"
|
||||
style={{
|
||||
[sideProperty === "right" ? "marginRight" : "marginLeft"]: `${sidebarWidth}px`,
|
||||
[sideProperty === "right" ? "marginRight" : "marginLeft"]: `${sidebarWidth - 8}px`, // reduce margin
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute top-0 bottom-0 w-[2px] bg-red-500/90 shadow-[0_0_8px_rgba(239,68,68,0.5)]"
|
||||
className="absolute top-3 bottom-3 w-[2px] bg-red-500/90 shadow-[0_0_8px_rgba(239,68,68,0.5)]"
|
||||
style={{
|
||||
[sideProperty]: `${offset}px`,
|
||||
}}
|
||||
>
|
||||
{/* Inverted triangle at top */}
|
||||
<div
|
||||
className="absolute -top-0.5 -left-[5px] w-0 h-0"
|
||||
style={{
|
||||
borderLeft: '6px solid transparent',
|
||||
borderRight: '6px solid transparent',
|
||||
borderTop: '8px solid rgb(239 68 68)',
|
||||
filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.3))',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="absolute -top-2 left-1/2 -translate-x-1/2 flex flex-col items-center"
|
||||
style={{ width: '32px' }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
background: '#ef4444',
|
||||
borderRadius: '12px 12px 12px 12px/14px 14px 8px 8px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.10)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '11px',
|
||||
fontWeight: 600,
|
||||
color: '#ef4444',
|
||||
letterSpacing: '-0.5px',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/* Subtle glow at top */}
|
||||
<div className="absolute -top-1 left-1/2 -translate-x-1/2 w-3 h-3 bg-red-500/30 rounded-full blur-sm" />
|
||||
<div className="absolute -top-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-red-500/30 rounded-full blur-sm" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -180,9 +195,11 @@ function PlaybackCursor({
|
||||
function TimelineAxis({
|
||||
intervalMs,
|
||||
videoDurationMs,
|
||||
currentTimeMs,
|
||||
}: {
|
||||
intervalMs: number;
|
||||
videoDurationMs: number;
|
||||
currentTimeMs: number;
|
||||
}) {
|
||||
const { sidebarWidth, direction, range, valueToPixels } = useTimelineContext();
|
||||
const sideProperty = direction === "rtl" ? "right" : "left";
|
||||
@@ -225,7 +242,7 @@ function TimelineAxis({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-10 bg-gradient-to-b from-slate-50 to-slate-100/50 border-b border-slate-200/60 relative overflow-hidden"
|
||||
className="h-10 bg-black border-b border-[#18181b] relative overflow-hidden"
|
||||
style={{
|
||||
[sideProperty === "right" ? "marginRight" : "marginLeft"]: `${sidebarWidth}px`,
|
||||
}}
|
||||
@@ -244,24 +261,33 @@ function TimelineAxis({
|
||||
|
||||
return (
|
||||
<div key={marker.time} style={markerStyle}>
|
||||
<div
|
||||
style={{
|
||||
width: "1px",
|
||||
height: "60%",
|
||||
backgroundColor: "#cbd5e1",
|
||||
opacity: 0.5,
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
paddingLeft: "4px",
|
||||
alignSelf: "flex-start",
|
||||
paddingTop: "3px",
|
||||
}}
|
||||
className="text-[10px] text-slate-500 font-medium select-none tracking-tight"
|
||||
>
|
||||
{marker.label}
|
||||
</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', height: '100%' }}>
|
||||
<div
|
||||
style={{
|
||||
width: '5px',
|
||||
height: '5px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: marker.time === currentTimeMs ? '#7c3aed' : '#94a3b8',
|
||||
boxShadow: marker.time === currentTimeMs ? '0 0 4px #7c3aed55' : 'none',
|
||||
marginRight: '5px',
|
||||
marginTop: '2px',
|
||||
transition: 'background 0.2s, box-shadow 0.2s',
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
fontWeight: marker.time === currentTimeMs ? 700 : 500,
|
||||
color: marker.time === currentTimeMs ? '#7c3aed' : '#94a3b8',
|
||||
fontSize: '11px',
|
||||
letterSpacing: '-0.5px',
|
||||
textShadow: marker.time === currentTimeMs ? '0 1px 6px #7c3aed33' : 'none',
|
||||
marginTop: '2px',
|
||||
}}
|
||||
className="select-none"
|
||||
>
|
||||
{marker.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -273,7 +299,7 @@ function Timeline({
|
||||
items,
|
||||
videoDurationMs,
|
||||
intervalMs,
|
||||
currentTimeMs,
|
||||
currentTimeMs,
|
||||
onSeek,
|
||||
onSelectZoom,
|
||||
selectedZoomId,
|
||||
@@ -308,10 +334,10 @@ function Timeline({
|
||||
<div
|
||||
ref={setTimelineRef}
|
||||
style={style}
|
||||
className="select-none bg-white min-h-[120px] relative cursor-pointer"
|
||||
className="select-none bg-black min-h-[120px] relative cursor-pointer"
|
||||
onClick={handleTimelineClick}
|
||||
>
|
||||
<TimelineAxis intervalMs={intervalMs} videoDurationMs={videoDurationMs} />
|
||||
<TimelineAxis intervalMs={intervalMs} videoDurationMs={videoDurationMs} currentTimeMs={currentTimeMs} />
|
||||
<PlaybackCursor currentTimeMs={currentTimeMs} videoDurationMs={videoDurationMs} />
|
||||
<Row id={ROW_ID}>
|
||||
{items.map((item) => (
|
||||
@@ -338,7 +364,6 @@ export default function TimelineEditor({
|
||||
zoomRegions,
|
||||
onZoomAdded,
|
||||
onZoomSpanChange,
|
||||
// Removed unused onZoomDelete prop
|
||||
selectedZoomId,
|
||||
onSelectZoom,
|
||||
}: TimelineEditorProps) {
|
||||
@@ -445,33 +470,33 @@ export default function TimelineEditor({
|
||||
|
||||
if (!videoDuration || videoDuration === 0) {
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center bg-gray-50 border border-gray-300 rounded-lg">
|
||||
<span className="text-gray-500 text-sm">Load a video to see timeline</span>
|
||||
<div className="flex-1 flex items-center justify-center rounded-lg">
|
||||
<span className="text-slate-400 text-sm">Load a video to see timeline</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col bg-white border border-slate-200 rounded-xl shadow-sm overflow-hidden">
|
||||
<div className="flex items-center gap-3 px-4 py-2.5 border-b border-slate-100 bg-gradient-to-b from-white to-slate-50/50">
|
||||
<Button onClick={handleAddZoom} variant="outline" size="sm" className="gap-2 h-8 px-3 text-xs">
|
||||
<Plus className="w-3.5 h-3.5" />
|
||||
<div className="flex-1 flex flex-col bg-black border border-none rounded-xl shadow-lg overflow-hidden">
|
||||
<div className="flex items-center gap-3 px-4 py-2.5">
|
||||
<Button onClick={handleAddZoom} variant="outline" size="sm" className="gap-2 h-8 px-3 text-xs bg-[#23232a] border-none text-slate-200 hover:bg-white hover:text-black">
|
||||
<Plus className="w-3.5 h-3.5 text-slate-400" />
|
||||
Add Zoom
|
||||
</Button>
|
||||
<div className="flex-1" />
|
||||
<div className="flex items-center gap-3 text-[10px] text-slate-400 font-medium">
|
||||
<span className="flex items-center gap-1">
|
||||
<kbd className="px-1.5 py-0.5 bg-slate-100 border border-slate-200 rounded text-slate-600">Command + Shift + Scroll</kbd>
|
||||
<kbd className="px-1.5 py-0.5 bg-[#23232a] border border-[#312e81] rounded text-slate-300">Command + Shift + Scroll</kbd>
|
||||
<span>Pan</span>
|
||||
</span>
|
||||
<span className="text-slate-300">•</span>
|
||||
<span className="text-slate-600">•</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<kbd className="px-1.5 py-0.5 bg-slate-100 border border-slate-200 rounded text-slate-600">Command + Scroll</kbd>
|
||||
<kbd className="px-1.5 py-0.5 bg-[#23232a] border border-[#312e81] rounded text-slate-300">Command + Scroll</kbd>
|
||||
<span>Zoom</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-x-auto overflow-y-hidden">
|
||||
<div className="mt-4 flex-1 overflow-x-auto overflow-y-hidden bg-[#000]">
|
||||
<TimelineWrapper
|
||||
range={clampedRange}
|
||||
videoDuration={videoDuration}
|
||||
|
||||
+17
-14
@@ -87,39 +87,42 @@
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #3b82f6;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 8px rgba(255,255,255,0.32);
|
||||
border: 2px solid #fff;
|
||||
transition: all 0.08s ease-out;
|
||||
}
|
||||
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb:hover {
|
||||
background: #2563eb;
|
||||
background: #fff;
|
||||
transform: scale(1.15);
|
||||
box-shadow: 0 3px 10px rgba(59, 130, 246, 0.5);
|
||||
box-shadow: 0 3px 10px rgba(255,255,255,0.5);
|
||||
border-color: #7c3aed;
|
||||
}
|
||||
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb:active {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
|
||||
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #3b82f6;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 2px 8px rgba(255,255,255,0.32);
|
||||
transition: all 0.08s ease-out;
|
||||
}
|
||||
|
||||
|
||||
input[type="range"]::-moz-range-thumb:hover {
|
||||
background: #2563eb;
|
||||
background: #fff;
|
||||
transform: scale(1.15);
|
||||
box-shadow: 0 3px 10px rgba(59, 130, 246, 0.5);
|
||||
box-shadow: 0 3px 10px rgba(255,255,255,0.5);
|
||||
border-color: #7c3aed;
|
||||
}
|
||||
|
||||
|
||||
input[type="range"]::-moz-range-thumb:active {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user