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}