@hyperframes/studio 0.8.53-desktop.0 → 0.8.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/assets/{hyperframes-player-PTEnAHhg.js → hyperframes-player-Cy_AwUa8.js} +1 -1
  2. package/dist/assets/{index-BjslOS9g.js → index-CTa-6Kso.js} +1 -1
  3. package/dist/assets/{index-CNyviK_l.js → index-DPUJoO8n.js} +203 -203
  4. package/dist/assets/{index--myXp8ui.js → index-DkAzKuPZ.js} +1 -1
  5. package/dist/assets/{index-BnXZuIJD.css → index-DrHQAuGO.css} +1 -1
  6. package/dist/index.d.ts +43 -8
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +7896 -7717
  9. package/dist/index.js.map +1 -1
  10. package/dist/styles.css +1 -0
  11. package/package.json +8 -7
  12. package/src/components/StudioHeader.tsx +1 -1
  13. package/src/components/nle/TimelinePane.tsx +4 -1
  14. package/src/components/renders/RenderQueue.test.tsx +133 -25
  15. package/src/components/renders/RenderQueue.tsx +99 -92
  16. package/src/components/renders/RenderQueueItem.tsx +69 -64
  17. package/src/components/ui/Select.tsx +4 -0
  18. package/src/headerExports.test.tsx +23 -0
  19. package/src/hooks/timelineAudioGroupCreate.ts +17 -1
  20. package/src/hooks/timelineAudioGroupVolume.ts +35 -2
  21. package/src/hooks/timelineElementFxAttribute.ts +34 -2
  22. package/src/hooks/useRightPanelIntents.ts +2 -1
  23. package/src/hooks/useTimelineAssetDropOps.test.tsx +62 -4
  24. package/src/hooks/useTimelineAssetDropOps.ts +40 -3
  25. package/src/hooks/useTimelineEditing.test.tsx +160 -2
  26. package/src/hooks/useTimelineEditing.ts +76 -39
  27. package/src/index.ts +5 -2
  28. package/src/main.tsx +26 -0
  29. package/src/player/components/Timeline.tsx +6 -6
  30. package/src/player/components/TimelineTrackHeader.test.tsx +5 -26
  31. package/src/player/components/TimelineTrackHeader.tsx +1 -1
  32. package/src/player/components/TimelineTypes.ts +3 -0
  33. package/src/player/components/timelineTheme.test.ts +4 -4
  34. package/src/player/components/timelineTheme.ts +30 -30
  35. package/src/player/components/usePublishRangeSelection.test.tsx +60 -0
  36. package/src/player/components/usePublishRangeSelection.ts +42 -0
  37. package/src/player/index.ts +1 -0
  38. package/src/player/store/playerStore.ts +8 -51
  39. package/src/player/store/rangeSelectionSlice.ts +13 -0
  40. package/src/player/store/timelineResetState.ts +51 -0
  41. package/src/styles/hex-baseline.json +2 -2
  42. package/src/styles/theme.css +0 -27
@@ -34,7 +34,7 @@ const GROUP_MEMBER_INDENT = 14;
34
34
  const GROUP_MEMBER_TINT = "rgba(255,255,255,0.035)";
35
35
 
36
36
  /** The gutter fill for a row, tinted when it belongs to a group. */
37
- export function gutterFill(base: string, isGroupMember: boolean): string {
37
+ function gutterFill(base: string, isGroupMember: boolean): string {
38
38
  return isGroupMember
39
39
  ? `linear-gradient(${GROUP_MEMBER_TINT}, ${GROUP_MEMBER_TINT}), ${base}`
40
40
  : base;
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { TimelineElement } from "../store/playerStore";
3
+ import type { TimelineTimeRange } from "../store/rangeSelectionSlice";
3
4
  import type { TimelineDropCallbacks } from "./timelineCallbacks";
4
5
  import type { TimelineTheme } from "./timelineTheme";
5
6
  import type { TimelineEditOverrides } from "./useResolvedTimelineEditCallbacks";
@@ -23,6 +24,8 @@ export interface TimelineProps extends TimelineDropCallbacks, TimelineEditOverri
23
24
  renderClipOverlay?: (element: TimelineElement) => ReactNode;
24
25
  onDeleteElement?: (element: TimelineElement) => Promise<void> | void;
25
26
  onSelectElement?: (element: TimelineElement | null) => void;
27
+ /** Notification only; null when cleared. The value lives in usePlayerStore.rangeSelection. */
28
+ onRangeSelect?: (range: TimelineTimeRange | null) => void;
26
29
  onCopyClip?: () => boolean;
27
30
  onPasteClip?: () => Promise<void>;
28
31
  onDuplicateClip?: () => Promise<boolean>;
@@ -9,10 +9,10 @@ import { getTrackStyle } from "./timelineIcons";
9
9
  describe("getTimelineTrackStyle", () => {
10
10
  it("uses one neutral clip style for every timeline tag", () => {
11
11
  const expectedStyle = {
12
- clip: "var(--timeline-track-clip-fill)",
13
- clipActive: "var(--timeline-track-clip-active)",
14
- accent: "var(--color-accent)",
15
- label: "var(--timeline-track-label)",
12
+ clip: "rgba(255,255,255,0.055)",
13
+ clipActive: "rgba(60,230,172,0.16)",
14
+ accent: "#3CE6AC",
15
+ label: "rgba(255,255,255,0.5)",
16
16
  };
17
17
 
18
18
  expect(getTimelineTrackStyle("video")).toEqual(expectedStyle);
@@ -36,43 +36,43 @@ export interface TimelineTheme {
36
36
  }
37
37
 
38
38
  const TRACK_STYLE: TimelineTrackStyle = {
39
- clip: "var(--timeline-track-clip-fill)",
40
- clipActive: "var(--timeline-track-clip-active)",
41
- accent: "var(--color-accent)",
42
- label: "var(--timeline-track-label)",
39
+ clip: "rgba(255,255,255,0.055)",
40
+ clipActive: "rgba(60,230,172,0.16)",
41
+ accent: "#3CE6AC",
42
+ label: "rgba(255,255,255,0.5)",
43
43
  };
44
44
 
45
- // Every field reads a CSS custom property (declared in styles/theme.css)
46
- // rather than a literal, so a host themes the timeline the same way it
47
- // themes the rest of Studio: by overriding the token, not this object.
48
45
  export const defaultTimelineTheme: TimelineTheme = {
49
- shellBackground: "var(--timeline-shell-bg)",
50
- shellBorder: "var(--timeline-shell-border)",
51
- rulerBorder: "var(--timeline-ruler-border)",
52
- rowBackground: "var(--timeline-row-bg)",
53
- rowBorder: "var(--timeline-row-border)",
54
- gutterBackground: "var(--timeline-gutter-bg)",
55
- gutterBorder: "var(--timeline-gutter-border)",
56
- textPrimary: "var(--timeline-text-primary)",
57
- textSecondary: "var(--timeline-text-secondary)",
58
- tickText: "var(--timeline-tick-text)",
59
- tickMajor: "var(--timeline-tick-major)",
60
- tickMinor: "var(--timeline-tick-minor)",
61
- clipBackground: "var(--timeline-clip-bg)",
62
- clipBackgroundActive: "var(--timeline-clip-bg-active)",
63
- clipBorder: "var(--timeline-clip-border)",
64
- clipBorderHover: "var(--timeline-clip-border-hover)",
65
- clipBorderActive: "var(--timeline-clip-border-active)",
66
- // Shadows stay literal: a light host still wants a dark contact shadow, so
67
- // these are not part of the surface/hairline/text/clip-colour theming ask.
46
+ // Near-black card surfaces: the panels sit dark while the shell canvas
47
+ // between them is a step LIGHTER (#18181B), so the gaps read as visible
48
+ // seams around dark cards (CapCut-style).
49
+ shellBackground: "#0A0A0B",
50
+ shellBorder: "rgba(255,255,255,0.05)",
51
+ rulerBorder: "rgba(255,255,255,0.16)",
52
+ // All track lanes use a single uniform color — one step lighter than the panel
53
+ // surface (#0A0A0B) so lanes are visibly distinct from the ruler/shell.
54
+ rowBackground: "#101014",
55
+ rowBorder: "rgba(255,255,255,0.06)",
56
+ gutterBackground: "#0E0F12",
57
+ gutterBorder: "rgba(255,255,255,0.10)",
58
+ textPrimary: "rgba(255,255,255,0.92)",
59
+ textSecondary: "rgba(255,255,255,0.62)",
60
+ tickText: "rgba(255,255,255,0.34)",
61
+ tickMajor: "rgba(255,255,255,0.10)",
62
+ tickMinor: "rgba(255,255,255,0.06)",
63
+ clipBackground: "#141922",
64
+ clipBackgroundActive: "#181e28",
65
+ clipBorder: "rgba(255,255,255,0.10)",
66
+ clipBorderHover: "rgba(255,255,255,0.18)",
67
+ clipBorderActive: "rgba(255,255,255,0.24)",
68
68
  clipShadow: "none",
69
69
  clipShadowHover: "0 2px 8px rgba(0,0,0,0.2)",
70
70
  clipShadowActive: "0 2px 8px rgba(0,0,0,0.2), 0 0 0 1px rgba(255,255,255,0.04)",
71
71
  clipShadowDragging: "0 8px 24px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.06)",
72
- handleColor: "var(--timeline-handle)",
73
- panelResizeSeam: "var(--timeline-resize-seam)",
74
- panelResizeActive: "var(--timeline-resize-active)",
75
- clipRadius: "var(--radius-lg)",
72
+ handleColor: "rgba(255,255,255,0.2)",
73
+ panelResizeSeam: "rgba(255,255,255,0.12)",
74
+ panelResizeActive: "rgba(255,255,255,0.24)",
75
+ clipRadius: "8px",
76
76
  };
77
77
 
78
78
  export function getTimelineTrackStyle(_tag: string): TimelineTrackStyle {
@@ -0,0 +1,60 @@
1
+ // @vitest-environment happy-dom
2
+ import React, { act } from "react";
3
+ import { createRoot } from "react-dom/client";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import { usePlayerStore } from "../store/playerStore";
6
+ import type { TimelineTimeRange } from "../store/rangeSelectionSlice";
7
+ import type { TimelineRangeSelection } from "./timelineEditing";
8
+ import { usePublishRangeSelection } from "./usePublishRangeSelection";
9
+
10
+ (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
11
+
12
+ function mount(onRangeSelect: (range: TimelineTimeRange | null) => void) {
13
+ const host = document.createElement("div");
14
+ const root = createRoot(host);
15
+ const Harness = ({ selection }: { selection: TimelineRangeSelection | null }) => {
16
+ usePublishRangeSelection(selection, onRangeSelect);
17
+ return null;
18
+ };
19
+ const render = (selection: TimelineRangeSelection | null) =>
20
+ act(() => root.render(<Harness selection={selection} />));
21
+ return { render, unmount: () => act(() => root.unmount()) };
22
+ }
23
+
24
+ const selection = (start: number, end: number): TimelineRangeSelection => ({
25
+ start,
26
+ end,
27
+ anchorX: 0,
28
+ anchorY: 0,
29
+ });
30
+
31
+ describe("usePublishRangeSelection", () => {
32
+ afterEach(() => usePlayerStore.setState({ rangeSelection: null }));
33
+
34
+ it("publishes an ordered range to the store and the host, and clears with null", () => {
35
+ const onRangeSelect = vi.fn();
36
+ const view = mount(onRangeSelect);
37
+
38
+ view.render(selection(3, 1));
39
+ expect(usePlayerStore.getState().rangeSelection).toEqual({ t0: 1, t1: 3 });
40
+ expect(onRangeSelect).toHaveBeenLastCalledWith({ t0: 1, t1: 3 });
41
+
42
+ view.render(null);
43
+ expect(usePlayerStore.getState().rangeSelection).toBeNull();
44
+ expect(onRangeSelect).toHaveBeenLastCalledWith(null);
45
+ view.unmount();
46
+ });
47
+
48
+ it("does not re-notify for an unchanged range and preserves a newer store value on unmount", () => {
49
+ const onRangeSelect = vi.fn();
50
+ const view = mount(onRangeSelect);
51
+
52
+ view.render(selection(1, 3));
53
+ view.render(selection(3, 1));
54
+ expect(onRangeSelect).toHaveBeenCalledTimes(1);
55
+
56
+ usePlayerStore.setState({ rangeSelection: { t0: 9, t1: 10 } });
57
+ view.unmount();
58
+ expect(usePlayerStore.getState().rangeSelection).toEqual({ t0: 9, t1: 10 });
59
+ });
60
+ });
@@ -0,0 +1,42 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { usePlayerStore } from "../store/playerStore";
3
+ import type { TimelineTimeRange } from "../store/rangeSelectionSlice";
4
+ import type { TimelineRangeSelection } from "./timelineEditing";
5
+
6
+ /** Mirrors the gesture's range into the store and notifies the host. */
7
+ export function usePublishRangeSelection(
8
+ rangeSelection: TimelineRangeSelection | null,
9
+ onRangeSelect?: (range: TimelineTimeRange | null) => void,
10
+ ) {
11
+ const onRangeSelectRef = useRef(onRangeSelect);
12
+ const publishedRangeRef = useRef<TimelineTimeRange | null>(null);
13
+
14
+ useEffect(() => {
15
+ onRangeSelectRef.current = onRangeSelect;
16
+ }, [onRangeSelect]);
17
+
18
+ useEffect(() => {
19
+ const range = rangeSelection
20
+ ? {
21
+ t0: Math.min(rangeSelection.start, rangeSelection.end),
22
+ t1: Math.max(rangeSelection.start, rangeSelection.end),
23
+ }
24
+ : null;
25
+ const store = usePlayerStore.getState();
26
+ const current = store.rangeSelection;
27
+ if (current?.t0 === range?.t0 && current?.t1 === range?.t1) return;
28
+ publishedRangeRef.current = range;
29
+ usePlayerStore.setState({ rangeSelection: range });
30
+ onRangeSelectRef.current?.(range);
31
+ }, [rangeSelection]);
32
+
33
+ useEffect(
34
+ () => () => {
35
+ const current = usePlayerStore.getState().rangeSelection;
36
+ const published = publishedRangeRef.current;
37
+ if (current?.t0 !== published?.t0 || current?.t1 !== published?.t1) return;
38
+ usePlayerStore.setState({ rangeSelection: null });
39
+ },
40
+ [],
41
+ );
42
+ }
@@ -12,6 +12,7 @@ export { resolveIframe } from "./lib/timelineDOM";
12
12
  // Store
13
13
  export { usePlayerStore, liveTime } from "./store/playerStore";
14
14
  export type { SelectElementOptions, TimelineElement, ZoomMode } from "./store/playerStore";
15
+ export type { TimelineTimeRange } from "./store/rangeSelectionSlice";
15
16
 
16
17
  // Utils
17
18
  export { formatTime } from "./lib/time";
@@ -2,7 +2,6 @@ import { create } from "zustand";
2
2
  import { attachPlayerStoreDevHandle } from "./playerStoreDevHandle";
3
3
  import { nextSelectionSet, revealTargetsSelection } from "./playerStoreSelection";
4
4
  import type { MusicBeatAnalysis } from "@hyperframes/core/beats";
5
- import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
6
5
  import type { BeatEditState } from "../../utils/beatEditing";
7
6
  import type { ClipManifestClip } from "../lib/playbackTypes";
8
7
  import {
@@ -11,7 +10,7 @@ import {
11
10
  type TimelineTimeDisplayMode,
12
11
  } from "../../utils/studioUiPreferences";
13
12
  import { clampTimelineZoomPercent, computePinnedZoomPercent } from "../components/timelineZoom";
14
- import { createKeyframeSlice, type KeyframeCacheEntry, type KeyframeSlice } from "./keyframeSlice";
13
+ import { createKeyframeSlice, type KeyframeSlice } from "./keyframeSlice";
15
14
  import {
16
15
  createAutomationSelectionSlice,
17
16
  type AutomationSelectionSlice,
@@ -19,10 +18,12 @@ import {
19
18
  import { createEditingModeSlice, type EditingModeSlice } from "./editingModeSlice";
20
19
  import { createTimelineFocusRequest, type TimelineFocusRequest } from "./timelineFocusState";
21
20
  import { createThumbnailSlice, type ThumbnailSlice } from "./thumbnailSlice";
22
- import { createPlaybackReadinessSlice, resetPlaybackReadinessState } from "./readinessSlice";
23
-
21
+ import { createPlaybackReadinessSlice } from "./readinessSlice";
22
+ import { createRangeSelectionSlice, type RangeSelectionSlice } from "./rangeSelectionSlice";
23
+ import { createTimelineResetState } from "./timelineResetState";
24
24
  export type { KeyframeCacheEntry } from "./keyframeSlice";
25
25
  export { liveTime } from "./liveTime";
26
+ export { createTimelineResetState };
26
27
 
27
28
  import type {
28
29
  TimelineElement,
@@ -59,7 +60,8 @@ type PlayerStoreSlices = KeyframeSlice &
59
60
  AutomationSelectionSlice &
60
61
  ThumbnailSlice &
61
62
  EditingModeSlice &
62
- ReturnType<typeof createPlaybackReadinessSlice>;
63
+ ReturnType<typeof createPlaybackReadinessSlice> &
64
+ RangeSelectionSlice;
63
65
  interface PlayerState extends PlayerStoreSlices {
64
66
  isPlaying: boolean;
65
67
  currentTime: number;
@@ -250,52 +252,6 @@ interface BeatHistoryEntry {
250
252
  label: string;
251
253
  }
252
254
 
253
- export function createTimelineResetState() {
254
- return {
255
- isPlaying: false,
256
- currentTime: 0,
257
- duration: 0,
258
- ...resetPlaybackReadinessState(),
259
- beatDragging: false,
260
- elements: [],
261
- selectedElementId: null,
262
- zEditVersion: 0,
263
- inPoint: null,
264
- outPoint: null,
265
- activeTool: "select" as const,
266
- activeKeyframePct: null,
267
- motionPathArmed: false,
268
- motionPathCreateAvailable: false,
269
- selectedKeyframes: new Set<string>(),
270
- // Ephemeral like every other selection here. A range surviving a project
271
- // switch can match a same-keyed clip in the new project and redirect a
272
- // paste through `sel.elementKey === paste.elementKey` to a stale t0.
273
- automationSelection: null,
274
- expandedClipIds: new Set<string>(),
275
- // Per-composition: ids from comp A match nothing in B, silencing all of it.
276
- collapsedGroupIds: new Set<string>(),
277
- expandedLaneOwnerIds: new Set<string>(),
278
- focusedEaseSegment: null,
279
- revealedAudioFxTarget: null,
280
- selectedElementIds: new Set<string>(),
281
- requestedSeekTime: null,
282
- lintFindingsByElement: new Map<string, { count: number; messages: string[] }>(),
283
- timelineFocus: null,
284
- keyframeCache: new Map<string, KeyframeCacheEntry>(),
285
- gsapAnimations: new Map<string, GsapAnimation[]>(),
286
- beatAnalysis: null,
287
- beatEdits: null,
288
- beatUndo: [],
289
- beatRedo: [],
290
- beatPersist: null,
291
- clipManifest: null,
292
- clipParentMap: new Map<string, string>(),
293
- topLevelIds: null,
294
- domClipChildren: [],
295
- subCompositionHostState: new Map<string, SubCompositionHostState>(),
296
- };
297
- }
298
-
299
255
  export const usePlayerStore = create<PlayerState>((set, get) => ({
300
256
  isPlaying: false,
301
257
  currentTime: 0,
@@ -328,6 +284,7 @@ export const usePlayerStore = create<PlayerState>((set, get) => ({
328
284
 
329
285
  ...createAutomationSelectionSlice(set),
330
286
  ...createEditingModeSlice(set),
287
+ ...createRangeSelectionSlice(),
331
288
  ...createPlaybackReadinessSlice(set),
332
289
 
333
290
  activeKeyframePct: null,
@@ -0,0 +1,13 @@
1
+ export interface TimelineTimeRange {
2
+ t0: number;
3
+ t1: number;
4
+ }
5
+
6
+ export interface RangeSelectionSlice {
7
+ /** Shift-drag ruler range in seconds, normalized so t0 <= t1. */
8
+ rangeSelection: TimelineTimeRange | null;
9
+ }
10
+
11
+ export function createRangeSelectionSlice(): RangeSelectionSlice {
12
+ return { rangeSelection: null };
13
+ }
@@ -0,0 +1,51 @@
1
+ import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
2
+ import type { KeyframeCacheEntry } from "./keyframeSlice";
3
+ import { resetPlaybackReadinessState } from "./readinessSlice";
4
+ import type { SubCompositionHostState } from "./timelineElement";
5
+
6
+ export function createTimelineResetState() {
7
+ return {
8
+ isPlaying: false,
9
+ currentTime: 0,
10
+ duration: 0,
11
+ ...resetPlaybackReadinessState(),
12
+ beatDragging: false,
13
+ elements: [],
14
+ selectedElementId: null,
15
+ zEditVersion: 0,
16
+ inPoint: null,
17
+ outPoint: null,
18
+ rangeSelection: null,
19
+ activeTool: "select" as const,
20
+ activeKeyframePct: null,
21
+ motionPathArmed: false,
22
+ motionPathCreateAvailable: false,
23
+ selectedKeyframes: new Set<string>(),
24
+ // Ephemeral like every other selection here. A range surviving a project
25
+ // switch can match a same-keyed clip in the new project and redirect a
26
+ // paste through `sel.elementKey === paste.elementKey` to a stale t0.
27
+ automationSelection: null,
28
+ expandedClipIds: new Set<string>(),
29
+ // Per-composition: ids from comp A match nothing in B, silencing all of it.
30
+ collapsedGroupIds: new Set<string>(),
31
+ expandedLaneOwnerIds: new Set<string>(),
32
+ focusedEaseSegment: null,
33
+ revealedAudioFxTarget: null,
34
+ selectedElementIds: new Set<string>(),
35
+ requestedSeekTime: null,
36
+ lintFindingsByElement: new Map<string, { count: number; messages: string[] }>(),
37
+ timelineFocus: null,
38
+ keyframeCache: new Map<string, KeyframeCacheEntry>(),
39
+ gsapAnimations: new Map<string, GsapAnimation[]>(),
40
+ beatAnalysis: null,
41
+ beatEdits: null,
42
+ beatUndo: [],
43
+ beatRedo: [],
44
+ beatPersist: null,
45
+ clipManifest: null,
46
+ clipParentMap: new Map<string, string>(),
47
+ topLevelIds: null,
48
+ domClipChildren: [],
49
+ subCompositionHostState: new Map<string, SubCompositionHostState>(),
50
+ };
51
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "total": 394,
2
+ "total": 418,
3
3
  "files": {
4
4
  "packages/studio/src/captions/components/CaptionOverlay.tsx": 5,
5
5
  "packages/studio/src/captions/components/CaptionTimeline.tsx": 11,
@@ -75,7 +75,7 @@
75
75
  "packages/studio/src/player/components/TimelineTrackHeader.tsx": 2,
76
76
  "packages/studio/src/player/components/TimelineTrackPlainHeader.tsx": 2,
77
77
  "packages/studio/src/player/components/VideoThumbnail.tsx": 6,
78
- "packages/studio/src/player/components/timelineTheme.ts": 5,
78
+ "packages/studio/src/player/components/timelineTheme.ts": 29,
79
79
  "packages/studio/src/player/components/trackHeaderLabelRows.tsx": 6,
80
80
  "packages/studio/src/styles/studio.css": 70,
81
81
  "packages/studio/src/telemetry/client.ts": 1,
@@ -46,33 +46,6 @@
46
46
  --color-brand-gradient-from: #06e3fa;
47
47
  --color-brand-gradient-to: #4fdb5e;
48
48
 
49
- /* The timeline's own surfaces: darker and bluer than the shared scale, so they
50
- * get their own names instead of snapping to the nearest --color-bg/-surface
51
- * step (AD147: a host themes the timeline by overriding these). */
52
- --timeline-shell-bg: #0a0a0b;
53
- --timeline-shell-border: rgba(255, 255, 255, 0.05);
54
- --timeline-ruler-border: rgba(255, 255, 255, 0.16);
55
- --timeline-row-bg: #101014;
56
- --timeline-row-border: rgba(255, 255, 255, 0.06);
57
- --timeline-gutter-bg: #0e0f12;
58
- --timeline-gutter-border: rgba(255, 255, 255, 0.1);
59
- --timeline-text-primary: rgba(255, 255, 255, 0.92);
60
- --timeline-text-secondary: rgba(255, 255, 255, 0.62);
61
- --timeline-tick-text: rgba(255, 255, 255, 0.34);
62
- --timeline-tick-major: rgba(255, 255, 255, 0.1);
63
- --timeline-tick-minor: rgba(255, 255, 255, 0.06);
64
- --timeline-clip-bg: #141922;
65
- --timeline-clip-bg-active: #181e28;
66
- --timeline-clip-border: rgba(255, 255, 255, 0.1);
67
- --timeline-clip-border-hover: rgba(255, 255, 255, 0.18);
68
- --timeline-clip-border-active: rgba(255, 255, 255, 0.24);
69
- --timeline-handle: rgba(255, 255, 255, 0.2);
70
- --timeline-resize-seam: rgba(255, 255, 255, 0.12);
71
- --timeline-resize-active: rgba(255, 255, 255, 0.24);
72
- --timeline-track-clip-fill: rgba(255, 255, 255, 0.055);
73
- --timeline-track-clip-active: rgba(60, 230, 172, 0.16);
74
- --timeline-track-label: rgba(255, 255, 255, 0.5);
75
-
76
49
  /* -------------------------------------------------------------- geometry */
77
50
 
78
51
  --radius-sm: 0.25rem;