diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 9bdf7c8..a2ee2d9 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -26,7 +26,6 @@ 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%)", diff --git a/src/components/video-editor/timeline/Item.tsx b/src/components/video-editor/timeline/Item.tsx index 4c9c79f..cfab696 100644 --- a/src/components/video-editor/timeline/Item.tsx +++ b/src/components/video-editor/timeline/Item.tsx @@ -1,6 +1,7 @@ import { useItem } from "dnd-timeline"; import type { Span } from "dnd-timeline"; import { cn } from "@/lib/utils"; +import { ZoomIn } from "lucide-react"; import glassStyles from "./ItemGlass.module.css"; interface ItemProps { @@ -10,9 +11,19 @@ interface ItemProps { children: React.ReactNode; isSelected?: boolean; onSelect?: () => void; + zoomDepth: number; } -export default function Item({ id, span, rowId, isSelected: _isSelected = false, onSelect }: ItemProps) { +// Map zoom depth to multiplier labels +const ZOOM_LABELS: Record = { + 1: "1.25×", + 2: "1.5×", + 3: "1.8×", + 4: "2.2×", + 5: "3.5×", +}; + +export default function Item({ id, span, rowId, isSelected = false, onSelect, zoomDepth }: ItemProps) { const { setNodeRef, attributes, listeners, itemStyle, itemContentStyle } = useItem({ id, span, @@ -26,13 +37,14 @@ export default function Item({ id, span, rowId, isSelected: _isSelected = false, {...listeners} {...attributes} onPointerDownCapture={() => onSelect?.()} - className={cn(glassStyles.itemDark)} + className="group" >
{ @@ -42,6 +54,14 @@ export default function Item({ id, span, rowId, isSelected: _isSelected = false, >
+ + {/* Content */} +
+ + + {ZOOM_LABELS[zoomDepth] || `${zoomDepth}×`} + +
diff --git a/src/components/video-editor/timeline/ItemGlass.module.css b/src/components/video-editor/timeline/ItemGlass.module.css index e5aa964..303a347 100644 --- a/src/components/video-editor/timeline/ItemGlass.module.css +++ b/src/components/video-editor/timeline/ItemGlass.module.css @@ -1,33 +1,55 @@ -.glassPurple { +.glassGreen { position: relative; - border-radius: 12px; + border-radius: 8px; -corner-smoothing: antialiased; - background: #34B27B; - border: none; - box-shadow: 0 2px 12px 0 rgba(52,178,123,0.14) inset, 0 2px 8px 0 rgba(0,0,0,0.10), 0 1px 6px 0 rgba(52,178,123,0.08); - margin: 0 4px; - backdrop-filter: blur(2px) saturate(120%); - -webkit-backdrop-filter: blur(2px) saturate(120%); + background: rgba(52, 178, 123, 0.15); + border: 1px solid rgba(52, 178, 123, 0.3); + box-shadow: 0 2px 12px 0 rgba(52, 178, 123, 0.1) inset; + margin: 2px 0; + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +.glassGreen:hover { + background: rgba(52, 178, 123, 0.25); + border-color: rgba(52, 178, 123, 0.5); + box-shadow: 0 4px 20px 0 rgba(52, 178, 123, 0.2) inset; +} + +.glassGreen.selected { + background: rgba(52, 178, 123, 0.35); + border-color: #34B27B; + box-shadow: 0 0 0 1px #34B27B, 0 4px 20px 0 rgba(52, 178, 123, 0.3) inset; + z-index: 10; } .zoomEndCap { position: absolute; top: 0; - background: #315d4a; - - width: 18px; - height: 100%; + bottom: 0; + background: #34B27B; + width: 4px; pointer-events: none; z-index: 2; - transition: background 0.2s, box-shadow 0.2s; + opacity: 0; + transition: opacity 0.2s, width 0.2s; +} + +.glassGreen:hover .zoomEndCap, +.glassGreen.selected .zoomEndCap { + opacity: 1; } .zoomEndCap.left { - left: -9px; + left: 0; cursor: ew-resize; + border-top-left-radius: 7px; + border-bottom-left-radius: 7px; } .zoomEndCap.right { - right: -9px; - transform: scaleX(-1); + right: 0; cursor: ew-resize; + border-top-right-radius: 7px; + border-bottom-right-radius: 7px; } diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx index 190546b..13a07ce 100644 --- a/src/components/video-editor/timeline/TimelineEditor.tsx +++ b/src/components/video-editor/timeline/TimelineEditor.tsx @@ -39,6 +39,7 @@ interface TimelineRenderItem { rowId: string; span: Span; label: string; + zoomDepth: number; } const SCALE_CANDIDATES = [ @@ -186,7 +187,7 @@ function TimelineAxis({ const markers = useMemo(() => { if (intervalMs <= 0) { - return [] as { time: number; label: string }[]; + return { markers: [], minorTicks: [] }; } const maxTime = videoDurationMs > 0 ? videoDurationMs : range.end; @@ -214,10 +215,27 @@ function TimelineAxis({ .filter(time => time <= maxTime) .sort((a, b) => a - b); - return sorted.map((time) => ({ - time, - label: formatTimeLabel(time, intervalMs), - })); + // Generate minor ticks (4 ticks between major intervals) + const minorTicks = []; + const minorInterval = intervalMs / 5; + + for (let time = firstMarker; time <= maxTime; time += minorInterval) { + if (time >= visibleStart && time <= visibleEnd) { + // Skip if it's close to a major marker + const isMajor = Math.abs(time % intervalMs) < 1; + if (!isMajor) { + minorTicks.push(time); + } + } + } + + return { + markers: sorted.map((time) => ({ + time, + label: formatTimeLabel(time, intervalMs), + })), + minorTicks + }; }, [intervalMs, range.end, range.start, videoDurationMs]); return ( @@ -227,7 +245,20 @@ function TimelineAxis({ [sideProperty === "right" ? "marginRight" : "marginLeft"]: `${sidebarWidth}px`, }} > - {markers.map((marker) => { + {/* Minor Ticks */} + {markers.minorTicks.map((time) => { + const offset = valueToPixels(time - range.start); + return ( +
+ ); + })} + + {/* Major Markers */} + {markers.markers.map((marker) => { const offset = valueToPixels(marker.time - range.start); const markerStyle: React.CSSProperties = { position: "absolute", @@ -242,7 +273,7 @@ function TimelineAxis({ return (
-
+
onSelectZoom?.(item.id)} + zoomDepth={item.zoomDepth} > {item.label} @@ -429,13 +461,20 @@ export default function TimelineEditor({ rowId: ROW_ID, span: { start: region.startMs, end: region.endMs }, label: `Zoom ${index + 1}`, + zoomDepth: region.depth, })); }, [zoomRegions]); if (!videoDuration || videoDuration === 0) { return ( -
- Load a video to see timeline +
+
+ +
+
+

No Video Loaded

+

Drag and drop a video to start editing

+
); }