diff --git a/src/components/video-editor/timeline/Item.module.css b/src/components/video-editor/timeline/Item.module.css new file mode 100644 index 0000000..941a86a --- /dev/null +++ b/src/components/video-editor/timeline/Item.module.css @@ -0,0 +1,4 @@ +.squircle { + border-radius: 12px; + -corner-smoothing: antialiased; +} diff --git a/src/components/video-editor/timeline/Item.tsx b/src/components/video-editor/timeline/Item.tsx index 5ee4f1e..775f96a 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 glassStyles from "./ItemGlass.module.css"; interface ItemProps { id: string; @@ -11,7 +12,7 @@ interface ItemProps { onSelect?: () => void; } -export default function Item({ id, span, rowId, children, isSelected = false, onSelect }: ItemProps) { +export default function Item({ id, span, rowId, isSelected = false, onSelect }: ItemProps) { const { setNodeRef, attributes, listeners, itemStyle, itemContentStyle } = useItem({ id, span, @@ -28,22 +29,18 @@ export default function Item({ id, span, rowId, children, isSelected = false, on >
{ event.stopPropagation(); onSelect?.(); }} > -
- - {children} - +
+
diff --git a/src/components/video-editor/timeline/ItemGlass.module.css b/src/components/video-editor/timeline/ItemGlass.module.css new file mode 100644 index 0000000..1ba88af --- /dev/null +++ b/src/components/video-editor/timeline/ItemGlass.module.css @@ -0,0 +1,32 @@ +.glassPurple { + 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%); + 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); + backdrop-filter: blur(2px) saturate(120%); + -webkit-backdrop-filter: blur(2px) saturate(120%); +} + +.zoomEndCap { + position: absolute; + top: 0; + background: #3c3c3c; + width: 18px; + height: 100%; + pointer-events: none; + z-index: 2; + transition: background 0.2s, box-shadow 0.2s; + +} + +.zoomEndCap.left { + left: -9px; + cursor: ew-resize; +} +.zoomEndCap.right { + right: -9px; + transform: scaleX(-1); + cursor: ew-resize; +} diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx index 2ade273..41da160 100644 --- a/src/components/video-editor/timeline/TimelineEditor.tsx +++ b/src/components/video-editor/timeline/TimelineEditor.tsx @@ -72,7 +72,8 @@ function calculateTimelineScale(durationSeconds: number): TimelineScaleConfig { const intervalMs = Math.round(selectedCandidate.intervalSeconds * 1000); const gridMs = Math.round(selectedCandidate.gridSeconds * 1000); - const minItemDurationMs = Math.max(100, Math.min(intervalMs, gridMs * 2)); + // Set minItemDurationMs to 1ms for maximum granularity + const minItemDurationMs = 1; const defaultItemDurationMs = Math.min( Math.max(minItemDurationMs, intervalMs * 2), totalMs > 0 ? totalMs : intervalMs * 2, @@ -337,7 +338,7 @@ export default function TimelineEditor({ zoomRegions, onZoomAdded, onZoomSpanChange, - onZoomDelete, + // Removed unused onZoomDelete prop selectedZoomId, onSelectZoom, }: TimelineEditorProps) { @@ -374,8 +375,14 @@ export default function TimelineEditor({ }, [zoomRegions, totalMs, safeMinDurationMs, onZoomSpanChange]); const hasOverlap = useCallback((newSpan: Span, excludeId?: string): boolean => { + // Snap if gap is 2ms or less return zoomRegions.some((region) => { if (region.id === excludeId) return false; + // If the new span is within 2ms of another region, treat as overlap (snap) + const gapBefore = newSpan.start - region.endMs; + const gapAfter = region.startMs - newSpan.end; + if (gapBefore > 0 && gapBefore <= 2) return true; + if (gapAfter > 0 && gapAfter <= 2) return true; return !(newSpan.end <= region.startMs || newSpan.start >= region.endMs); }); }, [zoomRegions]); diff --git a/src/components/video-editor/timeline/TimelineWrapper.tsx b/src/components/video-editor/timeline/TimelineWrapper.tsx index 874f5fe..3a89312 100644 --- a/src/components/video-editor/timeline/TimelineWrapper.tsx +++ b/src/components/video-editor/timeline/TimelineWrapper.tsx @@ -149,6 +149,8 @@ export default function TimelineWrapper({ [clampRange, onRangeChange, totalMs], ); + // To maximize granularity, disable grid snapping by not passing rangeGridSizeDefinition + // and allow pixel-level movement for items. return ( 0 ? gridSizeMs : undefined} + // Remove rangeGridSizeDefinition to avoid snap effect > {children}