@hyperframes/studio 0.8.104 → 0.8.105
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.
- package/dist/assets/{hyperframes-player-DAoaajuM.js → hyperframes-player-tnfCiVdR.js} +1 -1
- package/dist/assets/{index-BDK4rTMb.js → index-Ca6LZlVb.js} +1 -1
- package/dist/assets/index-Cre9X4RS.css +1 -0
- package/dist/assets/{index-De2aO4XM.js → index-DEDhPmPD.js} +205 -205
- package/dist/assets/{index-WV9LroAD.js → index-zpe332hb.js} +1 -1
- package/dist/{chunk-62DDWOSH.js → chunk-EEXSYY5F.js} +19 -18
- package/dist/chunk-EEXSYY5F.js.map +1 -0
- package/dist/{chunk-6GAO75IU.js → chunk-NCUBAVGK.js} +10 -8
- package/dist/chunk-NCUBAVGK.js.map +1 -0
- package/dist/{chunk-HIGHVWYD.js → chunk-QADHJFDF.js} +2 -2
- package/dist/index.css +6 -1
- package/dist/index.css.map +1 -1
- package/dist/index.html +2 -2
- package/dist/index.js +614 -638
- package/dist/index.js.map +1 -1
- package/dist/player.js +2 -2
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/ui.js +2 -2
- package/package.json +8 -8
- package/src/captions/components/CaptionAnimationPanel.tsx +2 -2
- package/src/captions/components/CaptionPropertyPanel.tsx +2 -2
- package/src/captions/components/CaptionTimeline.tsx +3 -3
- package/src/components/AskAgentModal.tsx +1 -1
- package/src/components/LintModal.tsx +35 -35
- package/src/components/StudioErrorBoundary.tsx +1 -1
- package/src/components/StudioHeader.dom.test.tsx +4 -4
- package/src/components/StudioHeader.tsx +2 -1
- package/src/components/StudioToast.tsx +7 -16
- package/src/components/TimelineToolPicker.tsx +1 -1
- package/src/components/TimelineToolbar.tsx +11 -11
- package/src/components/dock/Dock.tsx +1 -1
- package/src/components/dock/dock.css +9 -1
- package/src/components/editor/AnimationCard.tsx +5 -5
- package/src/components/editor/AnimationCardParts.tsx +1 -1
- package/src/components/editor/BlockParamsPanel.tsx +3 -3
- package/src/components/editor/BorderRadiusEditor.tsx +8 -15
- package/src/components/editor/CanvasContextMenu.tsx +1 -1
- package/src/components/editor/ComputedTweenNotice.tsx +1 -1
- package/src/components/editor/DomEditRotateHandle.tsx +1 -1
- package/src/components/editor/EaseCurveSection.tsx +14 -19
- package/src/components/editor/EaseModeControls.tsx +2 -2
- package/src/components/editor/EaseParamFields.tsx +8 -8
- package/src/components/editor/FileTree.tsx +1 -1
- package/src/components/editor/FileTreeIcons.tsx +47 -26
- package/src/components/editor/FileTreeNodes.tsx +11 -8
- package/src/components/editor/GestureRecordControl.tsx +3 -3
- package/src/components/editor/GsapAddAnimationControl.tsx +1 -1
- package/src/components/editor/GsapAnimationSection.tsx +2 -2
- package/src/components/editor/InlineTextToolbar.tsx +4 -4
- package/src/components/editor/InspectorHeaderActions.tsx +1 -1
- package/src/components/editor/KeyframeDiamond.tsx +6 -4
- package/src/components/editor/KeyframeNavigation.tsx +4 -4
- package/src/components/editor/LayersPanel.tsx +3 -3
- package/src/components/editor/PromotableControl.tsx +2 -2
- package/src/components/editor/PropertyPanelEmptyState.tsx +4 -4
- package/src/components/editor/PropertyPanelFlatFooter.tsx +2 -2
- package/src/components/editor/PropertyPanelFlatHeader.test.tsx +1 -1
- package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
- package/src/components/editor/SnapToolbar.tsx +5 -5
- package/src/components/editor/TimelineFxPopover.tsx +4 -4
- package/src/components/editor/Transform3DCube.tsx +1 -1
- package/src/components/editor/anchoredResizeReleaseShift.test.ts +0 -84
- package/src/components/editor/easeCurveSvg.tsx +1 -1
- package/src/components/editor/manualEditsDom.ts +3 -3
- package/src/components/editor/manualEditsDomPatches.test.ts +2 -86
- package/src/components/editor/manualEditsDomPatches.ts +0 -30
- package/src/components/editor/manualEditsSnapshot.ts +1 -0
- package/src/components/editor/persistSeam.integration.test.ts +0 -18
- package/src/components/editor/plainTranslate.ts +34 -3
- package/src/components/editor/propertyPanelColorCurveGraph.tsx +2 -2
- package/src/components/editor/propertyPanelColorGradingControls.tsx +1 -1
- package/src/components/editor/propertyPanelColorGradingSection.tsx +8 -8
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +1 -1
- package/src/components/editor/propertyPanelColorSecondary.tsx +4 -4
- package/src/components/editor/propertyPanelFill.tsx +6 -6
- package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
- package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +1 -1
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +6 -9
- package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +2 -2
- package/src/components/editor/propertyPanelFlatMotionSection.tsx +2 -2
- package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +2 -2
- package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +4 -3
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +1 -1
- package/src/components/editor/propertyPanelFlatSliderReadout.tsx +1 -1
- package/src/components/editor/propertyPanelFont.tsx +2 -2
- package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
- package/src/components/editor/propertyPanelFxControls.tsx +2 -2
- package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
- package/src/components/editor/propertyPanelFxNodeRow.tsx +1 -1
- package/src/components/editor/propertyPanelFxPresetRun.tsx +1 -1
- package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
- package/src/components/editor/propertyPanelPrimitives.tsx +1 -1
- package/src/components/editor/propertyPanelSections.tsx +2 -2
- package/src/components/editor/propertyPanelValueTier.test.ts +1 -1
- package/src/components/editor/propertyPanelValueTier.ts +1 -1
- package/src/components/feedback/StudioFeedbackCard.tsx +11 -23
- package/src/components/nle/AssetPreviewOverlay.tsx +1 -1
- package/src/components/nle/AudioMeterStrip.tsx +2 -2
- package/src/components/nle/NLEPreview.test.ts +8 -4
- package/src/components/nle/PreviewOverlays.tsx +1 -1
- package/src/components/nle/PreviewPane.tsx +3 -3
- package/src/components/nle/PreviewPoster.tsx +29 -1
- package/src/components/nle/PreviewZoomOverlay.tsx +1 -1
- package/src/components/panels/SlideshowPanel.tsx +4 -4
- package/src/components/panels/SlideshowSubPanels.tsx +19 -21
- package/src/components/panels/VariablesBindElement.tsx +1 -1
- package/src/components/panels/VariablesDeclarationForm.tsx +1 -1
- package/src/components/panels/VariablesPanel.tsx +4 -6
- package/src/components/panels/VariablesRowAction.tsx +1 -1
- package/src/components/renders/FfmpegRequiredNotice.tsx +4 -4
- package/src/components/renders/RenderQueue.tsx +2 -2
- package/src/components/renders/RenderQueueItem.tsx +3 -3
- package/src/components/sidebar/AssetCard.tsx +3 -3
- package/src/components/sidebar/AssetContextMenu.tsx +7 -7
- package/src/components/sidebar/AssetsTab.tsx +6 -6
- package/src/components/sidebar/AudioRow.tsx +5 -3
- package/src/components/sidebar/BlocksTab.tsx +6 -6
- package/src/components/sidebar/CompositionsTab.drag.test.tsx +3 -69
- package/src/components/sidebar/CompositionsTab.rootBadge.test.tsx +4 -50
- package/src/components/sidebar/CompositionsTab.thumbnails.test.tsx +256 -0
- package/src/components/sidebar/CompositionsTab.tsx +65 -12
- package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
- package/src/components/sidebar/SidebarLintButton.tsx +3 -3
- package/src/components/sidebar/compositionsTabTestUtils.tsx +39 -0
- package/src/components/timelineToolbarStyles.ts +3 -3
- package/src/components/ui/Button.tsx +4 -4
- package/src/components/ui/HyperframesLogo.tsx +19 -18
- package/src/components/ui/Input.tsx +1 -1
- package/src/components/ui/Menu.tsx +4 -2
- package/src/hooks/elementOffsetStager.ts +8 -7
- package/src/hooks/useClipboard.source.test.ts +42 -4
- package/src/hooks/useClipboard.ts +13 -3
- package/src/hooks/useDomEditCommits.ts +0 -3
- package/src/hooks/useDomGeometryCommit.test.tsx +4 -1
- package/src/hooks/useDomGeometryCommit.ts +0 -1
- package/src/hooks/useDomGeometryCommits.test.tsx +177 -32
- package/src/hooks/useDomGeometryCommits.ts +4 -49
- package/src/player/components/ClipContextMenu.tsx +1 -1
- package/src/player/components/CompositionThumbnail.tsx +23 -11
- package/src/player/components/EditModal.tsx +6 -6
- package/src/player/components/KeyframeDiamondContextMenu.tsx +2 -2
- package/src/player/components/PlayerControls.tsx +3 -3
- package/src/player/components/ShortcutsPanel.tsx +8 -20
- package/src/player/components/TimelineCanvas.tsx +1 -1
- package/src/player/components/TimelineClip.tsx +2 -2
- package/src/player/components/TimelineEmptyState.tsx +2 -2
- package/src/player/components/TimelineLanes.tsx +0 -1
- package/src/player/components/TrackClipCount.tsx +1 -1
- package/src/player/components/VolumeControl.tsx +1 -1
- package/src/player/components/timelineClipChildren.tsx +1 -1
- package/src/player/components/timelineMotionStyles.test.ts +6 -22
- package/src/player/components/trackHeaderLabelRows.tsx +16 -16
- package/src/player/lib/thumbnailScheduler.ts +4 -5
- package/src/player/lib/timelineViewportBudgets.test.ts +1 -1
- package/src/player/lib/timelineViewportBudgets.ts +1 -1
- package/src/styles/hex-baseline.json +7 -19
- package/src/styles/studio.css +0 -9
- package/src/styles/tailwind-preset.shared.js +2 -1
- package/src/styles/theme.css +204 -147
- package/src/styles/theme.test.ts +13 -9
- package/src/utils/blockCategories.ts +41 -9
- package/src/utils/clipboardPayload.ts +2 -0
- package/dist/assets/index-CQN3M56N.css +0 -1
- package/dist/chunk-62DDWOSH.js.map +0 -1
- package/dist/chunk-6GAO75IU.js.map +0 -1
- package/src/hooks/gsapTargetCache.ts +0 -62
- /package/dist/{chunk-HIGHVWYD.js.map → chunk-QADHJFDF.js.map} +0 -0
|
@@ -2,7 +2,6 @@ import type { RotationCommit } from "../components/editor/rotationDraft";
|
|
|
2
2
|
import { useCallback } from "react";
|
|
3
3
|
import { getDomEditTargetKey, type DomEditSelection } from "../components/editor/domEditing";
|
|
4
4
|
import {
|
|
5
|
-
applyStudioPathOffset,
|
|
6
5
|
applyStudioBoxSize,
|
|
7
6
|
captureStudioPathOffset,
|
|
8
7
|
captureStudioBoxSize,
|
|
@@ -17,35 +16,18 @@ import {
|
|
|
17
16
|
import { stageElementOffset } from "./elementOffsetStager";
|
|
18
17
|
import { savePlainRotation } from "./plainRotation";
|
|
19
18
|
import { prepareCropResize } from "../components/editor/cropResize";
|
|
20
|
-
import {
|
|
19
|
+
import { writePlainMove } from "../components/editor/plainTranslate";
|
|
21
20
|
import {
|
|
22
|
-
buildPathOffsetPatches,
|
|
23
21
|
buildBoxSizePatches,
|
|
24
22
|
buildClearPathOffsetPatches,
|
|
25
23
|
buildClearBoxSizePatches,
|
|
26
24
|
buildClearRotationPatches,
|
|
27
25
|
} from "../components/editor/manualEditsDomPatches";
|
|
28
26
|
import type { PatchOperation } from "../utils/sourcePatcher";
|
|
29
|
-
import { isElementGsapTargeted } from "./gsapTargetCache";
|
|
30
|
-
|
|
31
|
-
const GSAP_CSS_FALLBACK_BLOCKED_MESSAGE =
|
|
32
|
-
"This element is GSAP-animated — dragging via CSS would corrupt keyframes";
|
|
33
|
-
|
|
34
|
-
function rejectGsapCssFallback(
|
|
35
|
-
selection: DomEditSelection,
|
|
36
|
-
previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>,
|
|
37
|
-
showToast: (message: string, tone?: "error" | "info") => void,
|
|
38
|
-
): Promise<never> | null {
|
|
39
|
-
if (!isElementGsapTargeted(previewIframeRef.current, selection.element)) return null;
|
|
40
|
-
const error = new Error(GSAP_CSS_FALLBACK_BLOCKED_MESSAGE);
|
|
41
|
-
showToast(error.message, "error");
|
|
42
|
-
return Promise.reject(error);
|
|
43
|
-
}
|
|
44
27
|
|
|
45
28
|
// ── Hook ──
|
|
46
29
|
|
|
47
30
|
export interface UseDomGeometryCommitsParams {
|
|
48
|
-
previewIframeRef: React.MutableRefObject<HTMLIFrameElement | null>;
|
|
49
31
|
showToast: (message: string, tone?: "error" | "info") => void;
|
|
50
32
|
commitPositionPatchToHtml: (
|
|
51
33
|
selection: DomEditSelection,
|
|
@@ -56,7 +38,6 @@ export interface UseDomGeometryCommitsParams {
|
|
|
56
38
|
}
|
|
57
39
|
|
|
58
40
|
export function useDomGeometryCommits({
|
|
59
|
-
previewIframeRef,
|
|
60
41
|
showToast,
|
|
61
42
|
commitPositionPatchToHtml,
|
|
62
43
|
readOnlyPreview,
|
|
@@ -78,28 +59,6 @@ export function useDomGeometryCommits({
|
|
|
78
59
|
[commitPositionPatchToHtml, readOnlyPreview, showToast],
|
|
79
60
|
);
|
|
80
61
|
|
|
81
|
-
const handleDomPathOffsetCommit = useCallback(
|
|
82
|
-
(selection: DomEditSelection, next: { x: number; y: number }) => {
|
|
83
|
-
if (readOnlyPreview) return Promise.resolve();
|
|
84
|
-
// ponytail: GSAP-targeted elements are blocked (no SDK position-in-script op); CSS-path
|
|
85
|
-
// elements fall through to commitPositionPatchToHtml → persistDomEditOperations →
|
|
86
|
-
// onTrySdkPersist and are already SDK-cut-over as setStyle/setAttribute (§3.3 done).
|
|
87
|
-
// Upgrade path for GSAP: add a moveElementGsap SDK op in a separate SDK PR.
|
|
88
|
-
const gsapFallback = rejectGsapCssFallback(selection, previewIframeRef, showToast);
|
|
89
|
-
if (gsapFallback) return gsapFallback;
|
|
90
|
-
const before = captureStudioPathOffset(selection.element);
|
|
91
|
-
applyStudioPathOffset(selection.element, next);
|
|
92
|
-
return commitPositionPatchToHtml(selection, buildPathOffsetPatches(selection.element), {
|
|
93
|
-
label: "Move layer",
|
|
94
|
-
coalesceKey: `path-offset:${getDomEditTargetKey(selection)}`,
|
|
95
|
-
}).catch((error) => {
|
|
96
|
-
restoreStudioPathOffset(selection.element, before);
|
|
97
|
-
throw error;
|
|
98
|
-
});
|
|
99
|
-
},
|
|
100
|
-
[commitPositionPatchToHtml, previewIframeRef, showToast, readOnlyPreview],
|
|
101
|
-
);
|
|
102
|
-
|
|
103
62
|
const handleDomBoxSizeCommit = useCallback(
|
|
104
63
|
(
|
|
105
64
|
selection: DomEditSelection,
|
|
@@ -110,7 +69,7 @@ export function useDomGeometryCommits({
|
|
|
110
69
|
if (readOnlyPreview) return Promise.resolve();
|
|
111
70
|
const element = selection.element;
|
|
112
71
|
const beforeSize = captureStudioBoxSize(element);
|
|
113
|
-
const
|
|
72
|
+
const beforeOffset = captureStudioPathOffset(element);
|
|
114
73
|
const stageCrop = prepareCropResize(element);
|
|
115
74
|
applyStudioBoxSize(element, next);
|
|
116
75
|
const crop = stageCrop();
|
|
@@ -118,16 +77,13 @@ export function useDomGeometryCommits({
|
|
|
118
77
|
// (as a move writes it) that keeps the centre planted.
|
|
119
78
|
const patches = buildBoxSizePatches(element);
|
|
120
79
|
if (crop) patches.push(crop.patch);
|
|
121
|
-
if (offset)
|
|
122
|
-
writeTranslatePx(element, offset);
|
|
123
|
-
patches.push(translatePatch(offset));
|
|
124
|
-
}
|
|
80
|
+
if (offset) patches.push(...writePlainMove(element, offset));
|
|
125
81
|
return commitPositionPatchToHtml(selection, patches, {
|
|
126
82
|
label: "Resize layer box",
|
|
127
83
|
coalesceKey: `box-size:${getDomEditTargetKey(selection)}`,
|
|
128
84
|
}).catch((error) => {
|
|
129
85
|
restoreStudioBoxSize(element, beforeSize);
|
|
130
|
-
if (offset) element
|
|
86
|
+
if (offset) restoreStudioPathOffset(element, beforeOffset);
|
|
131
87
|
crop?.revert();
|
|
132
88
|
restore?.();
|
|
133
89
|
throw error;
|
|
@@ -173,7 +129,6 @@ export function useDomGeometryCommits({
|
|
|
173
129
|
|
|
174
130
|
return {
|
|
175
131
|
stageElementPositionOffset,
|
|
176
|
-
handleDomPathOffsetCommit,
|
|
177
132
|
handleDomBoxSizeCommit,
|
|
178
133
|
handleDomRotationCommit,
|
|
179
134
|
handleDomManualEditsReset,
|
|
@@ -201,7 +201,7 @@ export const ClipContextMenu = memo(function ClipContextMenu({
|
|
|
201
201
|
<button
|
|
202
202
|
type="button"
|
|
203
203
|
role="menuitem"
|
|
204
|
-
className="w-full flex items-center justify-between px-3 py-1.5 text-xs text-
|
|
204
|
+
className="w-full flex items-center justify-between px-3 py-1.5 text-xs text-danger-ink hover:bg-neutral-800 focus-visible:bg-neutral-800 outline-hidden cursor-pointer text-left"
|
|
205
205
|
onClick={() => {
|
|
206
206
|
onDelete(element);
|
|
207
207
|
onClose();
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { memo, useMemo, type CSSProperties } from "react";
|
|
2
2
|
import { useThumbnailLease } from "../../hooks/useThumbnailLease";
|
|
3
3
|
import { useThumbnailStripSize } from "../../hooks/useThumbnailStripSize";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createThumbnailKey,
|
|
6
|
+
type ThumbnailPriority,
|
|
7
|
+
type ThumbnailRequest,
|
|
8
|
+
} from "../lib/thumbnailScheduler";
|
|
5
9
|
import { TIMELINE_VIEWPORT_BUDGETS } from "../lib/timelineViewportBudgets";
|
|
6
10
|
import { computeThumbnailStrip, probeImageAspect } from "./thumbnailUtils";
|
|
7
11
|
|
|
@@ -85,6 +89,22 @@ export function compositionPathOfPreviewUrl(previewUrl: string): string {
|
|
|
85
89
|
return match?.[1] ? decodeURIComponent(match[1]) : "index.html";
|
|
86
90
|
}
|
|
87
91
|
|
|
92
|
+
export function compositionThumbnailRequest(
|
|
93
|
+
url: string,
|
|
94
|
+
projectId: string,
|
|
95
|
+
{ sessionEpoch = 0, priority = "visible", rich = false }: Partial<ThumbnailRequest> = {},
|
|
96
|
+
): ThumbnailRequest {
|
|
97
|
+
return {
|
|
98
|
+
key: createThumbnailKey({ kind: "composition", url }),
|
|
99
|
+
projectId,
|
|
100
|
+
sessionEpoch,
|
|
101
|
+
kind: "composition",
|
|
102
|
+
priority,
|
|
103
|
+
rich,
|
|
104
|
+
load: (signal: AbortSignal) => loadCompositionImage(url, signal),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
88
108
|
async function loadCompositionImage(url: string, signal: AbortSignal) {
|
|
89
109
|
const response = await fetch(url, { signal });
|
|
90
110
|
if (!response.ok) throw new Error(`Composition thumbnail failed (${response.status})`);
|
|
@@ -132,15 +152,7 @@ export const CompositionThumbnail = memo(function CompositionThumbnail({
|
|
|
132
152
|
contentRevision,
|
|
133
153
|
});
|
|
134
154
|
const request = useMemo(
|
|
135
|
-
() => ({
|
|
136
|
-
key: createThumbnailKey({ kind: "composition", url }),
|
|
137
|
-
projectId,
|
|
138
|
-
sessionEpoch,
|
|
139
|
-
kind: "composition" as const,
|
|
140
|
-
priority,
|
|
141
|
-
rich: true,
|
|
142
|
-
load: (signal: AbortSignal) => loadCompositionImage(url, signal),
|
|
143
|
-
}),
|
|
155
|
+
() => compositionThumbnailRequest(url, projectId, { sessionEpoch, priority, rich: true }),
|
|
144
156
|
[priority, projectId, sessionEpoch, url],
|
|
145
157
|
);
|
|
146
158
|
const snapshot = useThumbnailLease(request);
|
|
@@ -182,7 +194,7 @@ export const CompositionThumbnail = memo(function CompositionThumbnail({
|
|
|
182
194
|
</div>
|
|
183
195
|
)}
|
|
184
196
|
{snapshot.status === "loading" && (
|
|
185
|
-
<div className="absolute inset-0 animate-pulse bg-
|
|
197
|
+
<div className="absolute inset-0 animate-pulse bg-text-0/[0.035]" />
|
|
186
198
|
)}
|
|
187
199
|
{label && (
|
|
188
200
|
<div className="absolute inset-y-0 left-3 z-10 flex items-center">
|
|
@@ -147,7 +147,7 @@ export function EditPopover({ rangeStart, rangeEnd, anchorX, anchorY, onClose }:
|
|
|
147
147
|
<div className="px-4 py-2 border-b border-neutral-800/40 max-h-24 overflow-y-auto">
|
|
148
148
|
{elementsInRange.map((el) => (
|
|
149
149
|
<div key={el.id} className="flex items-center justify-between py-0.5">
|
|
150
|
-
<span className="text-[10px] font-mono text-
|
|
150
|
+
<span className="text-[10px] font-mono text-accent-ink/80">#{el.id}</span>
|
|
151
151
|
<span className="text-[10px] text-neutral-600">{el.tag}</span>
|
|
152
152
|
</div>
|
|
153
153
|
))}
|
|
@@ -174,7 +174,7 @@ export function EditPopover({ rangeStart, rangeEnd, anchorX, anchorY, onClose }:
|
|
|
174
174
|
|
|
175
175
|
{/* Action */}
|
|
176
176
|
{copyError && (
|
|
177
|
-
<p className="px-3 pb-2 text-[10px] text-
|
|
177
|
+
<p className="px-3 pb-2 text-[10px] text-danger-ink" role="alert">
|
|
178
178
|
Copy failed — check clipboard permissions and try again.
|
|
179
179
|
</p>
|
|
180
180
|
)}
|
|
@@ -184,7 +184,7 @@ export function EditPopover({ rangeStart, rangeEnd, anchorX, anchorY, onClose }:
|
|
|
184
184
|
disabled={!buildPromptCopyText(prompt)}
|
|
185
185
|
className={`py-1.5 text-[11px] font-medium rounded-lg transition-all border ${
|
|
186
186
|
copiedPromptOnly
|
|
187
|
-
? "bg-
|
|
187
|
+
? "bg-accent/15 text-accent-ink border-accent/30"
|
|
188
188
|
: "bg-neutral-800/70 text-neutral-200 border-neutral-700/50 hover:bg-neutral-800"
|
|
189
189
|
} disabled:opacity-50 disabled:cursor-not-allowed`}
|
|
190
190
|
>
|
|
@@ -194,13 +194,13 @@ export function EditPopover({ rangeStart, rangeEnd, anchorX, anchorY, onClose }:
|
|
|
194
194
|
onClick={handleCopy}
|
|
195
195
|
className={`py-1.5 text-[11px] font-medium rounded-lg transition-all ${
|
|
196
196
|
copiedAgentPrompt
|
|
197
|
-
? "bg-
|
|
198
|
-
: "bg-studio-accent/15 text-
|
|
197
|
+
? "bg-accent/15 text-accent-ink border border-accent/30"
|
|
198
|
+
: "bg-studio-accent/15 text-accent-ink border border-studio-accent/25 hover:bg-studio-accent/25"
|
|
199
199
|
}`}
|
|
200
200
|
>
|
|
201
201
|
{copiedAgentPrompt ? "Copied!" : "Copy to Agent"}
|
|
202
202
|
{!copiedAgentPrompt && (
|
|
203
|
-
<span className="text-[9px] text-
|
|
203
|
+
<span className="text-[9px] text-text-muted ml-1.5">Cmd+Enter</span>
|
|
204
204
|
)}
|
|
205
205
|
</button>
|
|
206
206
|
</div>
|
|
@@ -42,7 +42,7 @@ interface KeyframeDiamondContextMenuProps {
|
|
|
42
42
|
const ITEM_CLS =
|
|
43
43
|
"w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-200 hover:bg-neutral-800 focus-visible:bg-neutral-800 outline-hidden cursor-pointer text-left";
|
|
44
44
|
const DESTRUCTIVE_ITEM_CLS =
|
|
45
|
-
"w-full flex items-center gap-2 px-3 py-1.5 text-xs text-
|
|
45
|
+
"w-full flex items-center gap-2 px-3 py-1.5 text-xs text-danger-ink hover:bg-neutral-800 focus-visible:bg-neutral-800 outline-hidden cursor-pointer text-left";
|
|
46
46
|
|
|
47
47
|
export function KeyframeDiamondContextMenu({
|
|
48
48
|
state,
|
|
@@ -172,7 +172,7 @@ export function KeyframeDiamondContextMenu({
|
|
|
172
172
|
<button
|
|
173
173
|
type="button"
|
|
174
174
|
role="menuitem"
|
|
175
|
-
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-
|
|
175
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-danger-ink hover:bg-danger/25 focus-visible:bg-danger/25 outline-hidden cursor-pointer text-left"
|
|
176
176
|
onClick={() => {
|
|
177
177
|
onDeleteAll(state.element, state.animationId);
|
|
178
178
|
onClose();
|
|
@@ -76,7 +76,7 @@ const LoopButton = memo(function LoopButton({
|
|
|
76
76
|
}}
|
|
77
77
|
disabled={disabled}
|
|
78
78
|
className={`flex h-7 w-7 items-center justify-center rounded-md transition-colors disabled:opacity-30 ${
|
|
79
|
-
loopEnabled ? "text-
|
|
79
|
+
loopEnabled ? "text-accent-ink" : "text-neutral-500 hover:text-neutral-200"
|
|
80
80
|
}`}
|
|
81
81
|
aria-label={loopEnabled ? "Disable loop playback" : "Enable loop playback"}
|
|
82
82
|
aria-pressed={loopEnabled}
|
|
@@ -118,7 +118,7 @@ const FullscreenButton = memo(function FullscreenButton({
|
|
|
118
118
|
onToggleFullscreen();
|
|
119
119
|
}}
|
|
120
120
|
className={`flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors ${
|
|
121
|
-
isFullscreen ? "text-
|
|
121
|
+
isFullscreen ? "text-accent-ink" : "text-neutral-500 hover:text-neutral-200"
|
|
122
122
|
}`}
|
|
123
123
|
aria-label={isFullscreen ? "Exit fullscreen" : "Enter fullscreen"}
|
|
124
124
|
>
|
|
@@ -241,7 +241,7 @@ export const PlayerControls = memo(function PlayerControls({
|
|
|
241
241
|
<span ref={timeDisplayRef}>{formatTime(0)}</span>
|
|
242
242
|
{timeDisplayMode === "time" ? (
|
|
243
243
|
<>
|
|
244
|
-
<span className="mx-0.5 text-
|
|
244
|
+
<span className="mx-0.5 text-text-off">/</span>
|
|
245
245
|
<span className="text-neutral-600">{formatTime(duration)}</span>
|
|
246
246
|
</>
|
|
247
247
|
) : null}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useCallback, useEffect, useId, useRef, memo } from "react";
|
|
2
2
|
import { formatTime, frameToSeconds } from "../lib/time";
|
|
3
3
|
import { Tooltip } from "../../components/ui";
|
|
4
|
+
import { flatActive, flatIdle } from "../../components/timelineToolbarStyles";
|
|
4
5
|
import { useContextMenuDismiss } from "../../hooks/useContextMenuDismiss";
|
|
5
6
|
import { usePlayerStore } from "../store/playerStore";
|
|
6
7
|
import { DEFAULT_SHORTCUT_SECTIONS, type ShortcutSection } from "./studioShortcuts";
|
|
@@ -77,9 +78,7 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
|
|
|
77
78
|
ref={triggerRef}
|
|
78
79
|
type="button"
|
|
79
80
|
onClick={() => setShowShortcuts((v) => !v)}
|
|
80
|
-
className={
|
|
81
|
-
showShortcuts ? "text-neutral-200" : "text-neutral-600 hover:text-neutral-300"
|
|
82
|
-
}`}
|
|
81
|
+
className={showShortcuts ? flatActive : flatIdle}
|
|
83
82
|
aria-label="Shortcuts and tools"
|
|
84
83
|
aria-expanded={showShortcuts}
|
|
85
84
|
aria-controls={shortcutsPanelId}
|
|
@@ -110,10 +109,8 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
|
|
|
110
109
|
// Deliberately NOT aria-modal. This is a non-modal disclosure: focus is
|
|
111
110
|
// not trapped and the rest of the editor stays operable, so claiming
|
|
112
111
|
// modality would make assistive tech treat the whole app as inert.
|
|
113
|
-
className="absolute bottom-full right-0 mb-2 z-50 rounded-lg shadow-
|
|
112
|
+
className="absolute bottom-full right-0 mb-2 z-50 rounded-lg border border-border bg-raised shadow-popover min-w-[220px] overflow-y-auto outline-hidden"
|
|
114
113
|
style={{
|
|
115
|
-
background: "#161618",
|
|
116
|
-
border: "1px solid rgba(255,255,255,0.08)",
|
|
117
114
|
maxHeight: "min(280px, calc(100vh - 80px))",
|
|
118
115
|
}}
|
|
119
116
|
>
|
|
@@ -145,7 +142,7 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
|
|
|
145
142
|
</Tooltip>
|
|
146
143
|
</form>
|
|
147
144
|
</div>
|
|
148
|
-
<div
|
|
145
|
+
<div className="border-t border-border" />
|
|
149
146
|
<div className="px-3 pt-2.5 pb-2">
|
|
150
147
|
<p className="text-[9px] font-medium text-neutral-500 uppercase tracking-wider mb-1.5">
|
|
151
148
|
Work area
|
|
@@ -153,10 +150,7 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
|
|
|
153
150
|
<div className="flex flex-col gap-1">
|
|
154
151
|
<div className="flex items-center justify-between gap-2">
|
|
155
152
|
<div className="flex items-center gap-2">
|
|
156
|
-
<span
|
|
157
|
-
className="font-mono text-[10px] rounded-sm border border-neutral-700 px-1.5 py-0.5 text-neutral-300 min-w-[20px] text-center"
|
|
158
|
-
style={{ background: "rgba(255,255,255,0.05)" }}
|
|
159
|
-
>
|
|
153
|
+
<span className="font-mono text-[10px] rounded-sm border border-neutral-700 px-1.5 py-0.5 text-neutral-300 min-w-[20px] text-center bg-hover">
|
|
160
154
|
I
|
|
161
155
|
</span>
|
|
162
156
|
<span className="text-[10px] text-neutral-400">In-point</span>
|
|
@@ -194,10 +188,7 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
|
|
|
194
188
|
</div>
|
|
195
189
|
<div className="flex items-center justify-between gap-2">
|
|
196
190
|
<div className="flex items-center gap-2">
|
|
197
|
-
<span
|
|
198
|
-
className="font-mono text-[10px] rounded-sm border border-neutral-700 px-1.5 py-0.5 text-neutral-300 min-w-[20px] text-center"
|
|
199
|
-
style={{ background: "rgba(255,255,255,0.05)" }}
|
|
200
|
-
>
|
|
191
|
+
<span className="font-mono text-[10px] rounded-sm border border-neutral-700 px-1.5 py-0.5 text-neutral-300 min-w-[20px] text-center bg-hover">
|
|
201
192
|
O
|
|
202
193
|
</span>
|
|
203
194
|
<span className="text-[10px] text-neutral-400">Out-point</span>
|
|
@@ -235,7 +226,7 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
|
|
|
235
226
|
</div>
|
|
236
227
|
</div>
|
|
237
228
|
</div>
|
|
238
|
-
<div
|
|
229
|
+
<div className="border-t border-border" />
|
|
239
230
|
<div className="px-3 pt-2.5 pb-3 flex flex-col gap-3">
|
|
240
231
|
{sections.map((section, sectionIndex) => (
|
|
241
232
|
<div key={sectionIndex}>
|
|
@@ -245,10 +236,7 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
|
|
|
245
236
|
<div className="flex flex-col gap-1">
|
|
246
237
|
{section.hints.map((hint, hintIndex) => (
|
|
247
238
|
<div key={hintIndex} className="flex items-center gap-3">
|
|
248
|
-
<span
|
|
249
|
-
className="font-mono text-[10px] rounded-sm border border-neutral-700 px-1.5 py-0.5 text-neutral-300 min-w-[36px] text-center"
|
|
250
|
-
style={{ background: "rgba(255,255,255,0.05)" }}
|
|
251
|
-
>
|
|
239
|
+
<span className="font-mono text-[10px] rounded-sm border border-neutral-700 px-1.5 py-0.5 text-neutral-300 min-w-[36px] text-center bg-hover">
|
|
252
240
|
{hint.key}
|
|
253
241
|
</span>
|
|
254
242
|
<span className="text-[10px] text-neutral-400">{hint.label}</span>
|
|
@@ -83,7 +83,7 @@ export const TimelineCanvas = memo(function TimelineCanvas() {
|
|
|
83
83
|
data-active={ghostLaneActive || undefined}
|
|
84
84
|
className={`pointer-events-none absolute flex items-center justify-center rounded-lg border border-dashed text-[11px] transition-colors duration-150 ${
|
|
85
85
|
ghostLaneActive
|
|
86
|
-
? "border-studio-accent/60 bg-studio-accent/6 text-
|
|
86
|
+
? "border-studio-accent/60 bg-studio-accent/6 text-accent-ink"
|
|
87
87
|
: "border-neutral-700/50 text-neutral-500"
|
|
88
88
|
}`}
|
|
89
89
|
style={{
|
|
@@ -165,7 +165,7 @@ export const TimelineClip = memo(function TimelineClip({
|
|
|
165
165
|
width: 2,
|
|
166
166
|
borderRadius: 1,
|
|
167
167
|
background: "var(--clip-handle)",
|
|
168
|
-
opacity: handleOpacity
|
|
168
|
+
opacity: handleOpacity,
|
|
169
169
|
}}
|
|
170
170
|
/>
|
|
171
171
|
</div>
|
|
@@ -195,7 +195,7 @@ export const TimelineClip = memo(function TimelineClip({
|
|
|
195
195
|
width: 2,
|
|
196
196
|
borderRadius: 1,
|
|
197
197
|
background: "var(--clip-handle)",
|
|
198
|
-
opacity: handleOpacity
|
|
198
|
+
opacity: handleOpacity,
|
|
199
199
|
}}
|
|
200
200
|
/>
|
|
201
201
|
</div>
|
|
@@ -63,13 +63,13 @@ export function TimelineEmptyState({
|
|
|
63
63
|
strokeWidth="1.5"
|
|
64
64
|
strokeLinecap="round"
|
|
65
65
|
strokeLinejoin="round"
|
|
66
|
-
className="text-
|
|
66
|
+
className="text-accent-ink shrink-0"
|
|
67
67
|
>
|
|
68
68
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
69
69
|
<polyline points="7 10 12 15 17 10" />
|
|
70
70
|
<line x1="12" y1="15" x2="12" y2="3" />
|
|
71
71
|
</svg>
|
|
72
|
-
<span className="text-[13px] text-
|
|
72
|
+
<span className="text-[13px] text-accent-ink">Drop media files to import</span>
|
|
73
73
|
</>
|
|
74
74
|
) : (
|
|
75
75
|
<>
|
|
@@ -9,7 +9,7 @@ export function TrackClipCount({ clipCount }: { clipCount: number }) {
|
|
|
9
9
|
<span
|
|
10
10
|
aria-label={`${clipCount} clips`}
|
|
11
11
|
title={`${clipCount} clips`}
|
|
12
|
-
className="shrink-0 rounded-full bg-
|
|
12
|
+
className="shrink-0 rounded-full bg-hover px-1 text-[9px] leading-[14px] tabular-nums text-text-muted"
|
|
13
13
|
>
|
|
14
14
|
{clipCount}
|
|
15
15
|
</span>
|
|
@@ -93,7 +93,7 @@ export const VolumeControl = memo(function VolumeControl({
|
|
|
93
93
|
aria-label={muteLabel}
|
|
94
94
|
aria-pressed={silent}
|
|
95
95
|
className={`flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors disabled:pointer-events-none disabled:opacity-30 ${
|
|
96
|
-
silent ? "text-
|
|
96
|
+
silent ? "text-accent-ink" : "text-neutral-500 hover:text-neutral-200"
|
|
97
97
|
}`}
|
|
98
98
|
>
|
|
99
99
|
<VolumeIcon muted={audioMuted} volume={audioVolume} />
|
|
@@ -22,7 +22,7 @@ function ClipLintDot({ element }: { element: TimelineElement }) {
|
|
|
22
22
|
if (!lint || lint.count === 0) return null;
|
|
23
23
|
return (
|
|
24
24
|
<span
|
|
25
|
-
className="absolute w-1.5 h-1.5 rounded-full bg-
|
|
25
|
+
className="absolute w-1.5 h-1.5 rounded-full bg-warning-ink"
|
|
26
26
|
style={{ top: 7, right: 7 }}
|
|
27
27
|
title={lint.messages.join("\n")}
|
|
28
28
|
/>
|
|
@@ -39,18 +39,15 @@ function themeTokenValue(token: string): string {
|
|
|
39
39
|
|
|
40
40
|
describe("timeline motion styles", () => {
|
|
41
41
|
it.each([
|
|
42
|
-
["--timeline-track-label", "
|
|
43
|
-
["--timeline-tick-text", "
|
|
44
|
-
["--timeline-border-strong", "
|
|
45
|
-
["--timeline-group-member-tint", "
|
|
46
|
-
["--timeline-overlay-text", "
|
|
47
|
-
["--timeline-
|
|
48
|
-
["--timeline-playhead-glow", "rgba(60, 230, 172, 0.14)"],
|
|
49
|
-
["--timeline-playhead-shadow", "rgba(0, 0, 0, 0.55)"],
|
|
42
|
+
["--timeline-track-label", "var(--color-text-muted)"],
|
|
43
|
+
["--timeline-tick-text", "var(--color-text-2)"],
|
|
44
|
+
["--timeline-border-strong", "color-mix(in oklab, var(--color-text-0) 20%, transparent)"],
|
|
45
|
+
["--timeline-group-member-tint", "color-mix(in oklab, var(--color-text-0) 3.5%, transparent)"],
|
|
46
|
+
["--timeline-overlay-text", "var(--color-text-2)"],
|
|
47
|
+
["--timeline-playhead-glow", "color-mix(in oklab, var(--color-accent) 14%, transparent)"],
|
|
50
48
|
])("keeps the migrated default for %s", (token, expected) => {
|
|
51
49
|
expect(themeTokenValue(token)).toBe(expected);
|
|
52
50
|
});
|
|
53
|
-
|
|
54
51
|
it("keeps clip motion reduced-motion gated and layout safe", () => {
|
|
55
52
|
const mediaStart = studioCss.indexOf("@media (prefers-reduced-motion: no-preference)");
|
|
56
53
|
expect(mediaStart).toBeGreaterThanOrEqual(0);
|
|
@@ -93,13 +90,7 @@ describe("timeline motion styles", () => {
|
|
|
93
90
|
expect(timelineClipLabelRule).toContain("text-overflow: ellipsis");
|
|
94
91
|
expect(timelineClipLabelRule).toContain("max-width: calc(100% - 22px)");
|
|
95
92
|
expect(timelineClipTimecodeRule).toContain("color: var(--timeline-tick-text)");
|
|
96
|
-
expect(themeCss).toContain("--timeline-clip-bg: rgba(255, 255, 255, 0.12)");
|
|
97
|
-
expect(themeCss).toContain("--timeline-clip-border: rgba(255, 255, 255, 0.22)");
|
|
98
|
-
expect(themeCss).toContain("--timeline-track-label: rgba(255, 255, 255, 0.5)");
|
|
99
|
-
expect(themeCss).toContain("--timeline-tick-text: rgba(255, 255, 255, 0.34)");
|
|
100
|
-
expect(themeCss).toContain("--timeline-clip-label-active: rgba(232, 255, 247, 0.95)");
|
|
101
93
|
expect(themeCss).toContain("--timeline-clip-label-shadow: 0 1px 2px rgba(0, 0, 0, 0.85)");
|
|
102
|
-
expect(themeCss).toContain("--timeline-clip-selection: rgba(255, 255, 255, 0.85)");
|
|
103
94
|
expect(themeCss).toContain("--timeline-clip-selection-highlight: rgb(255 255 255 / 0.55)");
|
|
104
95
|
expect(themeCss).toContain("--timeline-clip-radius: 8px");
|
|
105
96
|
expect(themeCss).toContain("--timeline-clip-audio-radius: 21px");
|
|
@@ -108,18 +99,11 @@ describe("timeline motion styles", () => {
|
|
|
108
99
|
expect(themeCss).toContain("--timeline-clip-chip-bg: rgba(15, 22, 18, 0.73)");
|
|
109
100
|
expect(themeCss).toContain("--timeline-clip-chip-audio-bg: rgba(25, 16, 38, 0.7)");
|
|
110
101
|
expect(themeCss).toContain("--timeline-clip-chip-text: #ffffff");
|
|
111
|
-
expect(themeCss).toContain("--timeline-row-bg: #edf0ed");
|
|
112
102
|
expect(themeCss).toContain("--timeline-clip-shadow: 0 2px 6px rgb(0 0 0 / 0.12)");
|
|
113
|
-
expect(themeCss).toContain("--timeline-clip-selection: #0f766e");
|
|
114
|
-
expect(themeCss).toContain("--timeline-clip-audio-bg: rgba(167, 139, 250, 0.16)");
|
|
115
|
-
expect(themeCss).toContain("--timeline-clip-audio-border: rgba(167, 139, 250, 0.4)");
|
|
116
|
-
expect(themeCss).toContain("--timeline-clip-audio-bg-hover: rgba(167, 139, 250, 0.24)");
|
|
117
|
-
expect(themeCss).toContain("--timeline-clip-audio-bg-dragging: rgba(60, 52, 84, 0.96)");
|
|
118
103
|
expect(audioClipRule).toContain("background-color: var(--timeline-clip-audio-bg)");
|
|
119
104
|
expect(audioClipRule).toContain("border-color: var(--timeline-clip-audio-border)");
|
|
120
105
|
expect(audioClipHoverRule).toContain("background-color: var(--timeline-clip-audio-bg-hover)");
|
|
121
106
|
expect(audioClipDraggingRule).toContain("var(--timeline-clip-audio-bg-dragging)");
|
|
122
|
-
expect(themeCss).toContain("--timeline-clip-selection: rgba(255, 255, 255, 0.85)");
|
|
123
107
|
expect(selectedTimelineClipRule).toContain(
|
|
124
108
|
"outline: 1.5px solid var(--timeline-clip-selection)",
|
|
125
109
|
);
|
|
@@ -38,8 +38,8 @@ function PropertyGroupNavigation({
|
|
|
38
38
|
// target is met with a centered transparent ::before overlay instead of a
|
|
39
39
|
// bigger box; focus-visible matches every other control in this header.
|
|
40
40
|
const CHEVRON_BUTTON_CLASS =
|
|
41
|
-
"relative h-5 w-3 border-0 bg-transparent p-0 text-
|
|
42
|
-
"focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-
|
|
41
|
+
"relative h-5 w-3 border-0 bg-transparent p-0 text-text-2 hover:text-text-0 disabled:text-text-off " +
|
|
42
|
+
"focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-ring " +
|
|
43
43
|
"before:absolute before:left-1/2 before:top-1/2 before:h-6 before:w-6 " +
|
|
44
44
|
"before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']";
|
|
45
45
|
const seekTo = (keyframe: { percentage: number } | null) => {
|
|
@@ -120,7 +120,7 @@ export function PropertyGroupHeaderRow({
|
|
|
120
120
|
tabIndex={rovingTargetId === timelinePropertyRowId(elementId, lane.group) ? 0 : -1}
|
|
121
121
|
data-property-group={lane.group}
|
|
122
122
|
data-timeline-lane-top={getTimelineLaneTop(laneIndex)}
|
|
123
|
-
className="absolute left-0 flex items-center gap-1 overflow-hidden px-1.5 text-[10px] text-
|
|
123
|
+
className="absolute left-0 flex items-center gap-1 overflow-hidden px-1.5 text-[10px] text-text-2"
|
|
124
124
|
style={{
|
|
125
125
|
top: getTimelineLaneTop(laneIndex),
|
|
126
126
|
// The header column narrows to contentOrigin whenever that is under
|
|
@@ -134,12 +134,12 @@ export function PropertyGroupHeaderRow({
|
|
|
134
134
|
{/* Tree connector: vertical spine (top-half on the last lane) + branch tick. */}
|
|
135
135
|
<span className="relative h-full w-3 shrink-0" aria-hidden="true">
|
|
136
136
|
<span
|
|
137
|
-
className="absolute left-1.5 top-0 w-px bg-
|
|
137
|
+
className="absolute left-1.5 top-0 w-px bg-text-0/15"
|
|
138
138
|
style={{ height: isLastLane ? "50%" : "100%" }}
|
|
139
139
|
/>
|
|
140
|
-
<span className="absolute left-1.5 top-1/2 h-px w-1.5 bg-
|
|
140
|
+
<span className="absolute left-1.5 top-1/2 h-px w-1.5 bg-text-0/15" />
|
|
141
141
|
</span>
|
|
142
|
-
<span className="w-[46px] shrink-0 truncate text-
|
|
142
|
+
<span className="w-[46px] shrink-0 truncate text-text-0" title={label}>
|
|
143
143
|
{label}
|
|
144
144
|
</span>
|
|
145
145
|
<PropertyGroupNavigation
|
|
@@ -154,7 +154,7 @@ export function PropertyGroupHeaderRow({
|
|
|
154
154
|
aria-label={`${navigation.currentKeyframe ? "Remove" : "Add"} ${label} keyframe`}
|
|
155
155
|
title={`${navigation.currentKeyframe ? "Remove" : "Add"} ${label} keyframe`}
|
|
156
156
|
// h-6 w-6 = the 24x24 WCAG 2.2 minimum target; the ◆ glyph stays 11px.
|
|
157
|
-
className="flex h-6 w-6 shrink-0 items-center justify-center border-0 bg-transparent p-0 text-[11px] text-
|
|
157
|
+
className="flex h-6 w-6 shrink-0 items-center justify-center border-0 bg-transparent p-0 text-[11px] text-accent-ink focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-ring"
|
|
158
158
|
onClick={(event) => {
|
|
159
159
|
// Same as the disclosure caret and the eye: a control in the label
|
|
160
160
|
// column owns its click, it does not also hit the track row behind it.
|
|
@@ -168,7 +168,7 @@ export function PropertyGroupHeaderRow({
|
|
|
168
168
|
</button>
|
|
169
169
|
</PropertyGroupNavigation>
|
|
170
170
|
<span
|
|
171
|
-
className="min-w-0 flex-1 truncate text-right tabular-nums text-
|
|
171
|
+
className="min-w-0 flex-1 truncate text-right tabular-nums text-text-muted"
|
|
172
172
|
title={valueReadout(lane.group, values)}
|
|
173
173
|
>
|
|
174
174
|
{valueReadout(lane.group, values)}
|
|
@@ -231,7 +231,7 @@ export function AutomationLaneHeaderRow({
|
|
|
231
231
|
<div
|
|
232
232
|
data-automation-lane-label={label}
|
|
233
233
|
data-timeline-lane-top={top}
|
|
234
|
-
className="absolute left-0 flex items-center gap-1 overflow-hidden px-1.5 text-[10px] text-
|
|
234
|
+
className="absolute left-0 flex items-center gap-1 overflow-hidden px-1.5 text-[10px] text-text-2"
|
|
235
235
|
style={{
|
|
236
236
|
top,
|
|
237
237
|
width: columnWidth,
|
|
@@ -243,10 +243,10 @@ export function AutomationLaneHeaderRow({
|
|
|
243
243
|
tick at the name's own height. */}
|
|
244
244
|
<span className="relative h-full w-3 shrink-0" aria-hidden="true">
|
|
245
245
|
<span
|
|
246
|
-
className="absolute left-1.5 top-0 w-px bg-
|
|
246
|
+
className="absolute left-1.5 top-0 w-px bg-text-0/15"
|
|
247
247
|
style={{ height: isLastLane ? "50%" : "100%" }}
|
|
248
248
|
/>
|
|
249
|
-
<span className="absolute left-1.5 top-1/2 h-px w-1.5 bg-
|
|
249
|
+
<span className="absolute left-1.5 top-1/2 h-px w-1.5 bg-text-0/15" />
|
|
250
250
|
</span>
|
|
251
251
|
{/* Two lines: what the effect is, then which knob the envelope drives. On
|
|
252
252
|
one line a band's own name was the first thing truncated in a column this
|
|
@@ -262,7 +262,7 @@ export function AutomationLaneHeaderRow({
|
|
|
262
262
|
aria-label={onReveal ? `Show ${label} in the effect rack` : undefined}
|
|
263
263
|
title={onReveal ? `Show ${label} in the effect rack` : label}
|
|
264
264
|
disabled={!onReveal}
|
|
265
|
-
className="flex min-w-0 flex-1 flex-col justify-center rounded-sm border-0 bg-transparent p-0 text-left leading-tight enabled:hover:text-
|
|
265
|
+
className="flex min-w-0 flex-1 flex-col justify-center rounded-sm border-0 bg-transparent p-0 text-left leading-tight enabled:hover:text-text-0 focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-ring"
|
|
266
266
|
onPointerDown={(event) => event.stopPropagation()}
|
|
267
267
|
onClick={(event) => {
|
|
268
268
|
// The label column owns its click; it does not also fall through to
|
|
@@ -271,13 +271,13 @@ export function AutomationLaneHeaderRow({
|
|
|
271
271
|
onReveal?.();
|
|
272
272
|
}}
|
|
273
273
|
>
|
|
274
|
-
<span data-automation-lane-name="" className="truncate font-mono text-[9px] text-
|
|
274
|
+
<span data-automation-lane-name="" className="truncate font-mono text-[9px] text-text-2">
|
|
275
275
|
{name}
|
|
276
276
|
</span>
|
|
277
277
|
{param ? (
|
|
278
278
|
<span
|
|
279
279
|
data-automation-lane-param=""
|
|
280
|
-
className="truncate font-mono text-[9px] text-
|
|
280
|
+
className="truncate font-mono text-[9px] text-text-muted"
|
|
281
281
|
>
|
|
282
282
|
{param}
|
|
283
283
|
</span>
|
|
@@ -285,7 +285,7 @@ export function AutomationLaneHeaderRow({
|
|
|
285
285
|
{alsoAutomatedBy ? (
|
|
286
286
|
<span
|
|
287
287
|
data-automation-lane-also=""
|
|
288
|
-
className="truncate text-[9px] text-
|
|
288
|
+
className="truncate text-[9px] text-warning-ink"
|
|
289
289
|
title={`${alsoAutomatedBy} is also fading this — the two multiply.`}
|
|
290
290
|
>
|
|
291
291
|
{alsoAutomatedBy} is also fading this.
|
|
@@ -311,7 +311,7 @@ export function AutomationLaneHeaderRow({
|
|
|
311
311
|
aria-label={`Remove ${label} automation`}
|
|
312
312
|
title={`Remove ${label} automation`}
|
|
313
313
|
// h-6 w-6 is the 24x24 WCAG 2.2 target; the glyph stays small.
|
|
314
|
-
className="flex h-6 w-6 shrink-0 items-center justify-center border-0 bg-transparent p-0 text-[11px] text-
|
|
314
|
+
className="flex h-6 w-6 shrink-0 items-center justify-center border-0 bg-transparent p-0 text-[11px] text-text-muted hover:text-text-0 focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-ring"
|
|
315
315
|
onPointerDown={(event) => event.stopPropagation()}
|
|
316
316
|
onClick={(event) => {
|
|
317
317
|
// A control in the label column owns its click; it does not also hit
|