@hyperframes/studio 0.8.50 → 0.8.52
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-CxxDxHTW.js → hyperframes-player-I5YRzB02.js} +3 -3
- package/dist/assets/{index-Dt3qBI-9.js → index-BTAfgesM.js} +1 -1
- package/dist/assets/index-CxAu_Okg.css +1 -0
- package/dist/assets/index-DocJlW2v.js +376 -0
- package/dist/assets/{index-DUAIWxkI.js → index-Du5x7KUR.js} +1 -1
- package/dist/index.d.ts +419 -20
- package/dist/index.html +2 -2
- package/dist/index.js +7496 -8288
- package/dist/index.js.map +1 -1
- package/dist/styles/tailwind-preset.js.map +1 -1
- package/package.json +13 -12
- package/src/App.tsx +131 -143
- package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
- package/src/captions/components/CaptionOverlay.sender.test.tsx +52 -0
- package/src/captions/components/CaptionOverlay.tsx +8 -4
- package/src/captions/components/CaptionOverlayUtils.ts +2 -3
- package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
- package/src/captions/components/CaptionTimeline.tsx +1 -1
- package/src/captions/components/shared.tsx +2 -2
- package/src/components/AskAgentModal.tsx +5 -5
- package/src/components/EditorShell.tsx +8 -7
- package/src/components/ExternalFileConflictBanner.tsx +16 -16
- package/src/components/LintModal.tsx +6 -16
- package/src/components/MediaPreview.tsx +2 -2
- package/src/components/RightPanelTabs.test.tsx +151 -0
- package/src/components/RightPanelTabs.tsx +59 -0
- package/src/components/SaveQueuePausedBanner.tsx +2 -2
- package/src/components/StudioHeader.dom.test.tsx +181 -0
- package/src/components/StudioHeader.tsx +110 -210
- package/src/components/StudioLeftSidebar.tsx +7 -4
- package/src/components/StudioOverlays.tsx +1 -1
- package/src/components/StudioRightPanel.tsx +51 -39
- package/src/components/StudioToast.tsx +2 -2
- package/src/components/TimelineHistoryButtons.dom.test.tsx +117 -0
- package/src/components/TimelineHistoryButtons.tsx +54 -0
- package/src/components/TimelineToolbar.test.tsx +8 -0
- package/src/components/TimelineToolbar.tsx +8 -12
- package/src/components/dock/Dock.test.tsx +260 -0
- package/src/components/dock/Dock.tsx +281 -0
- package/src/components/dock/dock.css +28 -0
- package/src/components/dock/dockAccessibility.test.ts +166 -0
- package/src/components/dock/dockAccessibility.ts +98 -0
- package/src/components/dock/dockLayout.test.ts +134 -0
- package/src/components/dock/dockLayout.ts +105 -0
- package/src/components/dock/dockLayoutSchema.ts +66 -0
- package/src/components/dock/dockLayoutStore.ts +91 -0
- package/src/components/dock/dockTestHarness.ts +11 -0
- package/src/components/dock/panelRegistry.ts +67 -0
- package/src/components/editor/AnimationCard.tsx +4 -4
- package/src/components/editor/AnimationCardParts.tsx +6 -6
- package/src/components/editor/BlockParamsPanel.tsx +4 -4
- package/src/components/editor/BorderRadiusEditor.tsx +1 -1
- package/src/components/editor/CanvasContextMenu.tsx +1 -1
- package/src/components/editor/ComputedTweenNotice.tsx +1 -1
- package/src/components/editor/DomEditOverlay.tsx +1 -1
- package/src/components/editor/EaseCurveSection.tsx +1 -1
- package/src/components/editor/EaseParamFields.tsx +5 -5
- package/src/components/editor/FileTree.tsx +4 -4
- package/src/components/editor/FileTreeIcons.tsx +1 -1
- package/src/components/editor/FileTreeNodes.tsx +13 -13
- package/src/components/editor/InlineTextToolbar.tsx +1 -1
- package/src/components/editor/InspectorHeaderActions.tsx +3 -3
- package/src/components/editor/KeyframeDiamond.tsx +1 -1
- package/src/components/editor/KeyframeEaseList.tsx +1 -1
- package/src/components/editor/LayersPanel.test.ts +97 -1
- package/src/components/editor/LayersPanel.tsx +7 -5
- package/src/components/editor/MarqueeOverlay.tsx +1 -1
- package/src/components/editor/OffCanvasIndicators.tsx +1 -1
- package/src/components/editor/PreviewGuides.tsx +1 -1
- package/src/components/editor/PromotableControl.tsx +2 -2
- package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
- package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
- package/src/components/editor/SnapToolbar.tsx +2 -2
- package/src/components/editor/TimelineFxPopover.tsx +1 -1
- package/src/components/editor/TopologyLens.test.tsx +28 -0
- package/src/components/editor/TopologyLens.tsx +7 -5
- package/src/components/editor/Transform3DCube.tsx +2 -2
- package/src/components/editor/audioFxRevealTarget.ts +1 -0
- package/src/components/editor/domEditing.test.ts +1 -1
- package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
- package/src/components/editor/domEditingAgentPrompt.ts +4 -3
- package/src/components/editor/propertyPanelColor.tsx +11 -11
- package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
- package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
- package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
- package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
- package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
- package/src/components/editor/propertyPanelCommitField.tsx +1 -1
- package/src/components/editor/propertyPanelFill.tsx +4 -4
- package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
- package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
- package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
- package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
- package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
- package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
- package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
- package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
- package/src/components/editor/propertyPanelFont.tsx +5 -5
- package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
- package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
- package/src/components/editor/propertyPanelFxControls.tsx +6 -6
- package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
- package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
- package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
- package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
- package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
- package/src/components/editor/propertyPanelSections.tsx +8 -8
- package/src/components/editor/useColorGradingController.test.ts +38 -0
- package/src/components/editor/useColorGradingController.ts +9 -1
- package/src/components/editor/useColorGradingPreviews.test.ts +76 -0
- package/src/components/editor/useColorGradingPreviews.ts +11 -1
- package/src/components/editor/usePreviewCompositionRect.test.tsx +95 -0
- package/src/components/editor/usePreviewCompositionRect.ts +21 -0
- package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
- package/src/components/feedback/feedbackTrigger.ts +0 -1
- package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
- package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
- package/src/components/nle/NLEPreview.tsx +3 -3
- package/src/components/nle/PreviewOverlays.tsx +6 -6
- package/src/components/nle/PreviewPane.tsx +3 -3
- package/src/components/nle/TimelinePane.tsx +3 -3
- package/src/components/nle/TimelineResizeDivider.tsx +2 -2
- package/src/components/panels/SlideshowPanel.tsx +2 -2
- package/src/components/panels/SlideshowSubPanels.tsx +20 -17
- package/src/components/panels/VariablesBindElement.tsx +2 -2
- package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
- package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
- package/src/components/panels/VariablesPanel.tsx +3 -3
- package/src/components/panels/VariablesValueControls.tsx +2 -2
- package/src/components/pointerTargetSize.test.tsx +8 -0
- package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
- package/src/components/renders/RenderQueue.test.tsx +7 -0
- package/src/components/renders/RenderQueue.tsx +7 -7
- package/src/components/renders/RenderQueueItem.tsx +6 -6
- package/src/components/sidebar/AssetCard.tsx +8 -8
- package/src/components/sidebar/AssetContextMenu.tsx +8 -8
- package/src/components/sidebar/AssetsTab.tsx +2 -2
- package/src/components/sidebar/AudioRow.tsx +6 -6
- package/src/components/sidebar/BlocksTab.tsx +6 -6
- package/src/components/sidebar/CompositionsTab.tsx +5 -5
- package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
- package/src/components/sidebar/LeftSidebar.lintBadge.test.tsx +58 -0
- package/src/components/sidebar/LeftSidebar.tsx +19 -5
- package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
- package/src/components/timelineToolbarStyles.ts +5 -0
- package/src/components/ui/Button.test.tsx +252 -0
- package/src/components/ui/Button.tsx +89 -126
- package/src/components/ui/IconButton.tsx +38 -0
- package/src/components/ui/Input.tsx +115 -0
- package/src/components/ui/Menu.test.tsx +374 -0
- package/src/components/ui/Menu.tsx +213 -0
- package/src/components/ui/NumberField.tsx +122 -0
- package/src/components/ui/Popover.tsx +74 -0
- package/src/components/ui/SearchInput.tsx +2 -2
- package/src/components/ui/Select.tsx +100 -0
- package/src/components/ui/Slider.tsx +153 -0
- package/src/components/ui/Tabs.test.tsx +98 -0
- package/src/components/ui/Tabs.tsx +71 -0
- package/src/components/ui/Toggle.tsx +63 -0
- package/src/components/ui/Tooltip.test.tsx +110 -0
- package/src/components/ui/Tooltip.tsx +38 -112
- package/src/components/ui/cn.test.ts +30 -0
- package/src/components/ui/cn.ts +25 -0
- package/src/components/ui/index.ts +29 -2
- package/src/components/ui/mountHost.testHelpers.ts +24 -0
- package/src/components/ui/valueControls.test.tsx +524 -0
- package/src/hooks/useAppHotkeys.ts +21 -80
- package/src/hooks/useCaptionDetection.sender.test.tsx +46 -0
- package/src/hooks/useCaptionDetection.ts +3 -1
- package/src/hooks/useEditHistoryActions.test.tsx +90 -0
- package/src/hooks/useEditHistoryActions.ts +106 -0
- package/src/hooks/useLintModal.test.tsx +65 -0
- package/src/hooks/useLintModal.ts +3 -0
- package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
- package/src/hooks/useSdkSession.ts +43 -10
- package/src/hooks/useServerConnection.hashProject.test.tsx +94 -0
- package/src/hooks/useServerConnection.ts +43 -10
- package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
- package/src/hooks/useTimelineAssetDropOps.ts +52 -11
- package/src/index.ts +51 -0
- package/src/main.tsx +9 -3
- package/src/player/components/AudioWaveform.tsx +1 -1
- package/src/player/components/AutomationSelectionMenu.tsx +3 -3
- package/src/player/components/ClipContextMenu.tsx +3 -3
- package/src/player/components/CompositionThumbnail.test.ts +0 -3
- package/src/player/components/CompositionThumbnail.tsx +3 -4
- package/src/player/components/EditModal.tsx +1 -1
- package/src/player/components/ImageThumbnail.tsx +1 -1
- package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
- package/src/player/components/LayerDisclosureRow.tsx +2 -2
- package/src/player/components/PlayerControls.tsx +1 -1
- package/src/player/components/ShortcutsPanel.tsx +9 -9
- package/src/player/components/SpeedMenu.tsx +3 -5
- package/src/player/components/Timeline.tsx +2 -1
- package/src/player/components/TimelineCanvas.tsx +21 -14
- package/src/player/components/TimelineClip.tsx +0 -1
- package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
- package/src/player/components/TimelineEmptyState.tsx +5 -5
- package/src/player/components/TimelineFxButton.tsx +6 -6
- package/src/player/components/TimelineGroupHeader.tsx +5 -5
- package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
- package/src/player/components/TimelineGroupRow.tsx +1 -1
- package/src/player/components/TimelineLanes.test.tsx +32 -0
- package/src/player/components/TimelineLanes.tsx +2 -5
- package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
- package/src/player/components/TimelineRuler.tsx +1 -1
- package/src/player/components/TimelineShortcutHint.tsx +1 -1
- package/src/player/components/TimelineTrackHeader.tsx +1 -1
- package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
- package/src/player/components/TimelineTypes.ts +2 -1
- package/src/player/components/TrackGapContextMenu.tsx +1 -1
- package/src/player/components/VideoThumbnail.tsx +4 -2
- package/src/player/components/VolumeControl.tsx +2 -2
- package/src/player/components/timelineCallbacks.ts +8 -8
- package/src/player/components/timelineClipDragPreview.ts +51 -27
- package/src/player/components/timelineClipDragTypes.ts +3 -0
- package/src/player/components/timelineDragDrop.test.tsx +61 -1
- package/src/player/components/timelineDragDrop.ts +57 -15
- package/src/player/components/timelineLaneProps.ts +3 -0
- package/src/player/components/timelineSnapping.test.ts +27 -0
- package/src/player/components/timelineSnapping.ts +14 -1
- package/src/player/components/trackHeaderLabelRows.tsx +4 -4
- package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
- package/src/player/components/useTimelineClipDrag.ts +30 -3
- package/src/player/lib/describeClips.ts +3 -54
- package/src/player/lib/playbackShortcuts.ts +2 -0
- package/src/player/store/previewIframeStore.notify.test.tsx +75 -0
- package/src/player/store/previewIframeStore.test.tsx +29 -0
- package/src/player/store/previewIframeStore.ts +25 -0
- package/src/renderQueueExports.test.tsx +58 -0
- package/src/styles/classCandidates.test.ts +105 -0
- package/src/styles/classCandidates.ts +281 -0
- package/src/styles/hex-baseline.json +84 -0
- package/src/styles/hexRatchet.test.ts +201 -0
- package/src/styles/iconTokens.ts +20 -0
- package/src/styles/palette-reset.css +6 -0
- package/src/styles/studio.css +24 -21
- package/src/styles/styleSources.test.ts +20 -0
- package/src/styles/styleSources.ts +47 -0
- package/src/styles/tailwind-preset.shared.js +3 -0
- package/src/styles/theme.css +243 -0
- package/src/styles/theme.test.ts +183 -0
- package/src/styles/tokenGate.test.ts +109 -0
- package/src/utils/sourcePatcher.ts +1 -1
- package/src/utils/studioHelpers.test.ts +18 -0
- package/src/utils/studioHelpers.ts +11 -1
- package/src/utils/studioUiPreferences.ts +7 -0
- package/src/utils/timelineDropTrackInsert.test.ts +134 -0
- package/src/utils/timelineDropTrackInsert.ts +83 -0
- package/src/utils/typingTarget.ts +4 -0
- package/dist/assets/index-n2ml4tUn.js +0 -442
- package/dist/assets/index-pO6p0H27.css +0 -1
- package/src/components/PanelTabButton.tsx +0 -31
- package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
- package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
- package/src/components/storyboard/FramePoster.test.tsx +0 -61
- package/src/components/storyboard/FramePoster.tsx +0 -86
- package/src/components/storyboard/StoryboardDirection.tsx +0 -45
- package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
- package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
- package/src/components/storyboard/StoryboardGrid.tsx +0 -55
- package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
- package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
- package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
- package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
- package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
- package/src/components/storyboard/StoryboardView.tsx +0 -169
- package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
- package/src/components/storyboard/frameComments.test.ts +0 -95
- package/src/components/storyboard/frameComments.ts +0 -115
- package/src/components/storyboard/frameStatus.ts +0 -36
- package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
- package/src/components/storyboard/storyboardReviewStage.ts +0 -45
- package/src/components/storyboard/useFrameComments.ts +0 -98
- package/src/contexts/ViewModeContext.tsx +0 -142
- package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
- package/src/hooks/useProjectSignaturePoll.ts +0 -68
- package/src/hooks/useStoryboard.ts +0 -99
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import React, { act } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { normalizeHfColorGrading } from "@hyperframes/core/color-grading";
|
|
7
|
+
import { usePreviewIframeStore } from "../../player/store/previewIframeStore";
|
|
8
|
+
import { useColorGradingPreviews } from "./useColorGradingPreviews";
|
|
9
|
+
|
|
10
|
+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
usePreviewIframeStore.setState({ iframe: null });
|
|
14
|
+
document.body.innerHTML = "";
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function createFrame() {
|
|
18
|
+
return document.body.appendChild(document.createElement("iframe"));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function installRenderer(iframe: HTMLIFrameElement) {
|
|
22
|
+
const renderPreviews = vi.fn().mockResolvedValue({
|
|
23
|
+
width: 160,
|
|
24
|
+
height: 90,
|
|
25
|
+
images: [{ id: "bright-pop", dataUrl: "data:image/png;base64,x" }],
|
|
26
|
+
});
|
|
27
|
+
(iframe.contentWindow as unknown as { __hf: unknown }).__hf = {
|
|
28
|
+
colorGrading: { renderPreviews },
|
|
29
|
+
};
|
|
30
|
+
return renderPreviews;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("useColorGradingPreviews after the preview iframe is replaced", () => {
|
|
34
|
+
it("requests preset previews from the new iframe's runtime", async () => {
|
|
35
|
+
vi.useFakeTimers();
|
|
36
|
+
const a = createFrame();
|
|
37
|
+
const b = createFrame();
|
|
38
|
+
const renderOnB = installRenderer(b);
|
|
39
|
+
const ref = { current: a as HTMLIFrameElement | null };
|
|
40
|
+
usePreviewIframeStore.getState().setIframe(a);
|
|
41
|
+
const grading = normalizeHfColorGrading({ preset: "neutral" });
|
|
42
|
+
if (!grading) throw new Error("expected neutral grading");
|
|
43
|
+
const gradingRef = { current: grading };
|
|
44
|
+
const target = { selector: "#v" } as never;
|
|
45
|
+
const postColorGrading = vi.fn();
|
|
46
|
+
let requestPresetPreviews: () => void = () => {};
|
|
47
|
+
function Host() {
|
|
48
|
+
requestPresetPreviews = useColorGradingPreviews({
|
|
49
|
+
grading,
|
|
50
|
+
gradingRef,
|
|
51
|
+
identityKey: "k",
|
|
52
|
+
target,
|
|
53
|
+
previewIframeRef: ref,
|
|
54
|
+
postColorGrading,
|
|
55
|
+
}).requestPresetPreviews;
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const root = createRoot(document.body.appendChild(document.createElement("div")));
|
|
59
|
+
act(() => root.render(React.createElement(Host)));
|
|
60
|
+
act(() => requestPresetPreviews());
|
|
61
|
+
act(() => vi.advanceTimersByTime(0));
|
|
62
|
+
|
|
63
|
+
act(() => {
|
|
64
|
+
ref.current = b;
|
|
65
|
+
usePreviewIframeStore.getState().setIframe(b);
|
|
66
|
+
});
|
|
67
|
+
act(() => vi.advanceTimersByTime(0));
|
|
68
|
+
await act(async () => {
|
|
69
|
+
await Promise.resolve();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(renderOnB).toHaveBeenCalled();
|
|
73
|
+
act(() => root.unmount());
|
|
74
|
+
vi.useRealTimers();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useLivePreviewIframe } from "../../player/store/previewIframeStore";
|
|
1
2
|
import { useCallback, useEffect, useRef, useState, type RefObject } from "react";
|
|
2
3
|
import {
|
|
3
4
|
HF_COLOR_GRADING_ACTIVE_EFFECT_KEYS,
|
|
@@ -155,6 +156,7 @@ export function useColorGradingPreviews({
|
|
|
155
156
|
}
|
|
156
157
|
const previews = state.identityKey === identityKey ? state.previews : emptyPreviews();
|
|
157
158
|
|
|
159
|
+
const livePreviewIframe = useLivePreviewIframe();
|
|
158
160
|
useEffect(() => {
|
|
159
161
|
if (!request) return;
|
|
160
162
|
const { kind } = request;
|
|
@@ -245,7 +247,15 @@ export function useColorGradingPreviews({
|
|
|
245
247
|
iframe.removeEventListener("load", attempt);
|
|
246
248
|
window.removeEventListener("message", onMessage);
|
|
247
249
|
};
|
|
248
|
-
}, [
|
|
250
|
+
}, [
|
|
251
|
+
grading.effects,
|
|
252
|
+
grading.lut,
|
|
253
|
+
grading.palette,
|
|
254
|
+
previewIframeRef,
|
|
255
|
+
livePreviewIframe,
|
|
256
|
+
request,
|
|
257
|
+
target,
|
|
258
|
+
]);
|
|
249
259
|
|
|
250
260
|
const stopAnimatedPreview = useCallback(() => {
|
|
251
261
|
const session = animatedRef.current;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { act, useRef } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { usePreviewIframeStore } from "../../player/store/previewIframeStore";
|
|
7
|
+
import {
|
|
8
|
+
usePreviewCompositionRect,
|
|
9
|
+
type PreviewCompositionRect,
|
|
10
|
+
} from "./usePreviewCompositionRect";
|
|
11
|
+
|
|
12
|
+
(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
usePreviewIframeStore.getState().setIframe(null);
|
|
16
|
+
document.body.innerHTML = "";
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function boxed<T extends HTMLElement>(el: T, box: [number, number, number, number]): T {
|
|
20
|
+
const [left, top, width, height] = box;
|
|
21
|
+
el.getBoundingClientRect = () => new DOMRect(left, top, width, height);
|
|
22
|
+
return el;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function mountProbe() {
|
|
26
|
+
let rect: PreviewCompositionRect | undefined;
|
|
27
|
+
function Probe() {
|
|
28
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
29
|
+
rect = usePreviewCompositionRect(ref);
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
ref={(el) => {
|
|
33
|
+
ref.current = el;
|
|
34
|
+
if (el) boxed(el, [10, 20, 1000, 600]);
|
|
35
|
+
}}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const host = document.createElement("div");
|
|
40
|
+
document.body.append(host);
|
|
41
|
+
act(() => createRoot(host).render(<Probe />));
|
|
42
|
+
return () => rect;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function compositionFrame(box: [number, number, number, number], width: number, height: number) {
|
|
46
|
+
const iframe = boxed(document.createElement("iframe"), box);
|
|
47
|
+
document.body.append(iframe);
|
|
48
|
+
const root = iframe.contentDocument?.createElement("div");
|
|
49
|
+
root?.setAttribute("data-composition-id", "main");
|
|
50
|
+
root?.setAttribute("data-width", String(width));
|
|
51
|
+
root?.setAttribute("data-height", String(height));
|
|
52
|
+
if (root) iframe.contentDocument?.body.append(root);
|
|
53
|
+
return iframe;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const settle = () => new Promise((r) => setTimeout(r, 400));
|
|
57
|
+
|
|
58
|
+
describe("usePreviewCompositionRect", () => {
|
|
59
|
+
it("reports the live preview iframe's box relative to the overlay, scaled to the composition", async () => {
|
|
60
|
+
const read = mountProbe();
|
|
61
|
+
const iframe = compositionFrame([110, 70, 960, 540], 1920, 1080);
|
|
62
|
+
await act(async () => {
|
|
63
|
+
usePreviewIframeStore.getState().setIframe(iframe);
|
|
64
|
+
await settle();
|
|
65
|
+
});
|
|
66
|
+
expect(read()).toEqual({
|
|
67
|
+
left: 100,
|
|
68
|
+
top: 50,
|
|
69
|
+
width: 960,
|
|
70
|
+
height: 540,
|
|
71
|
+
scaleX: 0.5,
|
|
72
|
+
scaleY: 0.5,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("follows the iframe when a reload replaces it", async () => {
|
|
77
|
+
const read = mountProbe();
|
|
78
|
+
await act(async () => {
|
|
79
|
+
usePreviewIframeStore.getState().setIframe(compositionFrame([110, 70, 960, 540], 1920, 1080));
|
|
80
|
+
await settle();
|
|
81
|
+
});
|
|
82
|
+
await act(async () => {
|
|
83
|
+
usePreviewIframeStore.getState().setIframe(compositionFrame([60, 40, 500, 500], 1000, 1000));
|
|
84
|
+
await settle();
|
|
85
|
+
});
|
|
86
|
+
expect(read()).toEqual({
|
|
87
|
+
left: 50,
|
|
88
|
+
top: 20,
|
|
89
|
+
width: 500,
|
|
90
|
+
height: 500,
|
|
91
|
+
scaleX: 0.5,
|
|
92
|
+
scaleY: 0.5,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useMemo, type RefObject } from "react";
|
|
2
|
+
import { usePreviewIframeStore } from "../../player/store/previewIframeStore";
|
|
3
|
+
import { useDomEditCompositionRect } from "./useDomEditCompositionRect";
|
|
4
|
+
|
|
5
|
+
export type { DomEditCompositionRect as PreviewCompositionRect } from "./useDomEditCompositionRect";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Where the composition sits inside `overlayRef`, in overlay pixels, plus the scale from
|
|
9
|
+
* composition units to pixels. Follows the live preview iframe across reloads.
|
|
10
|
+
*/
|
|
11
|
+
export function usePreviewCompositionRect(overlayRef: RefObject<HTMLDivElement | null>) {
|
|
12
|
+
const iframeRef = useMemo<RefObject<HTMLIFrameElement | null>>(
|
|
13
|
+
() => ({
|
|
14
|
+
get current() {
|
|
15
|
+
return usePreviewIframeStore.getState().iframe;
|
|
16
|
+
},
|
|
17
|
+
}),
|
|
18
|
+
[],
|
|
19
|
+
);
|
|
20
|
+
return useDomEditCompositionRect({ iframeRef, overlayRef });
|
|
21
|
+
}
|
|
@@ -243,7 +243,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
243
243
|
if (req) trackStudioFeedbackInterviewClick({ reason: req.reason });
|
|
244
244
|
close("sent");
|
|
245
245
|
}}
|
|
246
|
-
className="h-6
|
|
246
|
+
className="h-6 shrink-0 whitespace-nowrap rounded-full border border-white/20 bg-white/8 px-2.5 text-[11px] leading-6 text-neutral-100 transition-[background-color,border-color,transform] duration-150 ease-out hover:border-white/35 hover:bg-white/16 active:scale-[0.97] motion-reduce:transition-none"
|
|
247
247
|
>
|
|
248
248
|
Talk to us, 30 min
|
|
249
249
|
</a>
|
|
@@ -251,7 +251,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
251
251
|
<button
|
|
252
252
|
type="button"
|
|
253
253
|
onClick={() => close("close")}
|
|
254
|
-
className="-mr-1 -mt-0.5 flex h-5 w-5
|
|
254
|
+
className="-mr-1 -mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-md text-neutral-500 transition-colors duration-150 hover:bg-white/10 hover:text-neutral-200"
|
|
255
255
|
aria-label="Dismiss"
|
|
256
256
|
>
|
|
257
257
|
<svg
|
|
@@ -283,7 +283,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
283
283
|
key={n}
|
|
284
284
|
// A resting fill, so the row reads as eleven controls rather
|
|
285
285
|
// than as a line of text that happens to be clickable.
|
|
286
|
-
className="flex h-7 cursor-pointer items-center justify-center rounded-md bg-white/
|
|
286
|
+
className="flex h-7 cursor-pointer items-center justify-center rounded-md bg-white/4 text-[11px] tabular-nums text-neutral-400 transition-[background-color,color,transform] duration-150 ease-out hover:bg-white/[0.14] hover:text-neutral-100 active:scale-[0.97] has-checked:bg-white/90 has-checked:text-neutral-900 has-focus-visible:ring-1 has-focus-visible:ring-white/50 motion-reduce:transition-none"
|
|
287
287
|
>
|
|
288
288
|
<input
|
|
289
289
|
type="radio"
|
|
@@ -328,8 +328,8 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
328
328
|
// attached to nothing.
|
|
329
329
|
className={`h-6 rounded-full border px-2.5 text-[11px] transition-[background-color,border-color,color,transform] duration-150 ease-out active:scale-[0.97] motion-reduce:transition-none ${
|
|
330
330
|
failed
|
|
331
|
-
? "border-red-300/20 bg-red-100/
|
|
332
|
-
: "border-white/10 bg-white/
|
|
331
|
+
? "border-red-300/20 bg-red-100/6 text-red-100/80 hover:border-red-200/70 hover:bg-red-100/25 hover:text-white"
|
|
332
|
+
: "border-white/10 bg-white/4 text-neutral-300 hover:border-white/60 hover:bg-white/25 hover:text-white"
|
|
333
333
|
}`}
|
|
334
334
|
>
|
|
335
335
|
{preset.label}
|
|
@@ -355,12 +355,12 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
355
355
|
placeholder={followUp.placeholder}
|
|
356
356
|
aria-label={followUp.prompt}
|
|
357
357
|
maxLength={500}
|
|
358
|
-
className="h-7 min-w-0 flex-1 rounded-md border border-white/10 bg-black/20 px-2 text-[11px] text-neutral-100 outline-
|
|
358
|
+
className="h-7 min-w-0 flex-1 rounded-md border border-white/10 bg-black/20 px-2 text-[11px] text-neutral-100 outline-hidden transition-colors duration-150 placeholder:text-neutral-500 focus:border-white/25"
|
|
359
359
|
/>
|
|
360
360
|
<button
|
|
361
361
|
type="button"
|
|
362
362
|
onClick={() => submit()}
|
|
363
|
-
className="h-7
|
|
363
|
+
className="h-7 shrink-0 rounded-md bg-white/10 px-2.5 text-[11px] text-neutral-100 transition-[background-color,transform] duration-150 ease-out hover:bg-white/16 active:scale-[0.97] motion-reduce:transition-none"
|
|
364
364
|
>
|
|
365
365
|
Send
|
|
366
366
|
</button>
|
|
@@ -76,7 +76,6 @@ const FOLLOW_UPS: readonly FollowUpQuestion[] = [
|
|
|
76
76
|
{ label: "Layers panel", hint: "The layer tree on the right" },
|
|
77
77
|
{ label: "Variables panel", hint: "Composition variables on the right" },
|
|
78
78
|
{ label: "Blocks browser", hint: "The block library in the sidebar" },
|
|
79
|
-
{ label: "Storyboard mode", hint: "The storyboard view next to Preview" },
|
|
80
79
|
{ label: "Caption editing", hint: "Editing caption words and presets in Studio" },
|
|
81
80
|
{ label: "Render history", hint: "The list of past exports" },
|
|
82
81
|
{ label: "Nothing", hint: "It all earns its place" },
|
|
@@ -42,7 +42,11 @@ function AssetPreviewMedia({
|
|
|
42
42
|
}) {
|
|
43
43
|
if (kind === "image") {
|
|
44
44
|
return (
|
|
45
|
-
<img
|
|
45
|
+
<img
|
|
46
|
+
src={serveUrl}
|
|
47
|
+
alt={name}
|
|
48
|
+
className="max-w-full max-h-[40vh] rounded-sm object-contain"
|
|
49
|
+
/>
|
|
46
50
|
);
|
|
47
51
|
}
|
|
48
52
|
if (kind === "video") {
|
|
@@ -53,7 +57,7 @@ function AssetPreviewMedia({
|
|
|
53
57
|
autoPlay
|
|
54
58
|
muted
|
|
55
59
|
playsInline
|
|
56
|
-
className="max-w-full max-h-[40vh] rounded"
|
|
60
|
+
className="max-w-full max-h-[40vh] rounded-sm"
|
|
57
61
|
/>
|
|
58
62
|
);
|
|
59
63
|
}
|
|
@@ -21,7 +21,7 @@ export function CompositionBreadcrumb({ stack, onNavigate }: CompositionBreadcru
|
|
|
21
21
|
return (
|
|
22
22
|
<nav
|
|
23
23
|
aria-label="Composition navigation"
|
|
24
|
-
className="flex items-center gap-1 px-2 h-8 border-b border-neutral-800/50 bg-neutral-900/50
|
|
24
|
+
className="flex items-center gap-1 px-2 h-8 border-b border-neutral-800/50 bg-neutral-900/50 shrink-0"
|
|
25
25
|
>
|
|
26
26
|
{/* Back button — always goes to parent */}
|
|
27
27
|
<button
|
|
@@ -33,7 +33,7 @@ export function CompositionBreadcrumb({ stack, onNavigate }: CompositionBreadcru
|
|
|
33
33
|
});
|
|
34
34
|
onNavigate(stack.length - 2);
|
|
35
35
|
}}
|
|
36
|
-
className="flex items-center gap-1 px-1.5 py-0.5 rounded text-xs text-neutral-400 hover:text-white hover:bg-neutral-800 active:scale-[0.98] transition-colors"
|
|
36
|
+
className="flex items-center gap-1 px-1.5 py-0.5 rounded-sm text-xs text-neutral-400 hover:text-white hover:bg-neutral-800 active:scale-[0.98] transition-colors"
|
|
37
37
|
title="Back (Esc, or double-click empty timeline)"
|
|
38
38
|
aria-label="Back to parent composition"
|
|
39
39
|
>
|
|
@@ -45,7 +45,7 @@ export function CompositionBreadcrumb({ stack, onNavigate }: CompositionBreadcru
|
|
|
45
45
|
const isLast = i === stack.length - 1;
|
|
46
46
|
return (
|
|
47
47
|
<span key={level.id} className="flex items-center gap-1">
|
|
48
|
-
{i > 0 && <CaretRight size={10} className="text-neutral-600
|
|
48
|
+
{i > 0 && <CaretRight size={10} className="text-neutral-600 shrink-0" />}
|
|
49
49
|
{isLast ? (
|
|
50
50
|
<span className="text-xs text-neutral-200 font-medium">{level.label}</span>
|
|
51
51
|
) : (
|
|
@@ -442,7 +442,7 @@ export const NLEPreview = memo(function NLEPreview({
|
|
|
442
442
|
<div className="flex flex-col h-full min-h-0">
|
|
443
443
|
<div
|
|
444
444
|
ref={viewportRef}
|
|
445
|
-
className="relative flex-1 flex items-center justify-center p-2 overflow-hidden min-h-0 outline-
|
|
445
|
+
className="relative flex-1 flex items-center justify-center p-2 overflow-hidden min-h-0 outline-hidden focus:ring-1 focus:ring-studio-accent/40 bg-[var(--studio-preview-bg,var(--color-neutral-950))]"
|
|
446
446
|
tabIndex={0}
|
|
447
447
|
aria-label="Composition preview"
|
|
448
448
|
>
|
|
@@ -492,14 +492,14 @@ export const NLEPreview = memo(function NLEPreview({
|
|
|
492
492
|
</div>
|
|
493
493
|
<div
|
|
494
494
|
ref={hudRef}
|
|
495
|
-
className="pointer-events-none absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 rounded-lg px-4 py-2 text-sm font-mono tabular-nums text-white/90 bg-black/60 backdrop-blur-
|
|
495
|
+
className="pointer-events-none absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 rounded-lg px-4 py-2 text-sm font-mono tabular-nums text-white/90 bg-black/60 backdrop-blur-xs shadow-lg"
|
|
496
496
|
style={{ opacity: 0, transition: "opacity 200ms ease-in" }}
|
|
497
497
|
aria-live="polite"
|
|
498
498
|
/>
|
|
499
499
|
{!isPreviewAtFit(settledZoom) && (
|
|
500
500
|
<button
|
|
501
501
|
type="button"
|
|
502
|
-
className="absolute bottom-3 right-3 z-50 rounded-md px-2.5 py-1 text-xs font-medium text-white/80 bg-black/50 backdrop-blur-
|
|
502
|
+
className="absolute bottom-3 right-3 z-50 rounded-md px-2.5 py-1 text-xs font-medium text-white/80 bg-black/50 backdrop-blur-xs hover:bg-black/70 hover:text-white transition-colors"
|
|
503
503
|
onClick={() => applyZoom(DEFAULT_PREVIEW_ZOOM)}
|
|
504
504
|
aria-label="Reset zoom to fit"
|
|
505
505
|
data-testid="preview-reset-zoom"
|
|
@@ -222,13 +222,13 @@ export function PreviewOverlays({
|
|
|
222
222
|
<TopologyLens iframeRef={previewIframeRef} activeCompositionPath={activeCompPath} />
|
|
223
223
|
<CaptionOverlay iframeRef={previewIframeRef} />
|
|
224
224
|
{/* Mode indicator + explicit exit */}
|
|
225
|
-
<div className="absolute top-2 left-1/2 -translate-x-1/2 z-
|
|
225
|
+
<div className="absolute top-2 left-1/2 -translate-x-1/2 z-60 flex items-center gap-2 rounded-full border border-studio-accent/40 bg-black/70 px-2.5 py-1">
|
|
226
226
|
<span className="h-1.5 w-1.5 rounded-full bg-studio-accent" aria-hidden="true" />
|
|
227
227
|
<span className="text-2xs text-neutral-200">Editing captions</span>
|
|
228
228
|
<button
|
|
229
229
|
type="button"
|
|
230
230
|
onClick={exitCaptionMode}
|
|
231
|
-
className="rounded text-2xs text-neutral-400 underline underline-offset-2 hover:text-neutral-100 focus-visible:outline focus-visible:outline-1 focus-visible:outline-studio-accent"
|
|
231
|
+
className="rounded-sm text-2xs text-neutral-400 underline underline-offset-2 hover:text-neutral-100 focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-studio-accent"
|
|
232
232
|
>
|
|
233
233
|
Exit
|
|
234
234
|
</button>
|
|
@@ -236,13 +236,13 @@ export function PreviewOverlays({
|
|
|
236
236
|
{captionSyncError && (
|
|
237
237
|
<div
|
|
238
238
|
role="alert"
|
|
239
|
-
className="absolute top-10 left-1/2 -translate-x-1/2 z-
|
|
239
|
+
className="absolute top-10 left-1/2 -translate-x-1/2 z-60 flex items-center gap-2 rounded-full border border-red-500/50 bg-red-950/90 px-2.5 py-1"
|
|
240
240
|
>
|
|
241
241
|
<span className="text-2xs text-red-200">{captionSyncError}</span>
|
|
242
242
|
<button
|
|
243
243
|
type="button"
|
|
244
244
|
onClick={() => useCaptionStore.getState().retrySave?.()}
|
|
245
|
-
className="rounded text-2xs text-red-100 underline underline-offset-2 hover:text-white"
|
|
245
|
+
className="rounded-sm text-2xs text-red-100 underline underline-offset-2 hover:text-white"
|
|
246
246
|
>
|
|
247
247
|
Retry
|
|
248
248
|
</button>
|
|
@@ -250,7 +250,7 @@ export function PreviewOverlays({
|
|
|
250
250
|
type="button"
|
|
251
251
|
onClick={() => useCaptionStore.getState().setSyncError(null)}
|
|
252
252
|
aria-label="Dismiss"
|
|
253
|
-
className="rounded px-0.5 text-2xs text-red-300/70 hover:text-red-100"
|
|
253
|
+
className="rounded-sm px-0.5 text-2xs text-red-300/70 hover:text-red-100"
|
|
254
254
|
>
|
|
255
255
|
✕
|
|
256
256
|
</button>
|
|
@@ -338,7 +338,7 @@ export function PreviewOverlays({
|
|
|
338
338
|
<button
|
|
339
339
|
type="button"
|
|
340
340
|
onClick={enterCaptionMode}
|
|
341
|
-
className="absolute top-2 left-1/2 -translate-x-1/2 z-
|
|
341
|
+
className="absolute top-2 left-1/2 -translate-x-1/2 z-60 rounded-full border border-neutral-700 bg-black/60 px-2.5 py-1 text-2xs text-neutral-300 transition-colors hover:border-studio-accent/50 hover:text-studio-accent focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-studio-accent"
|
|
342
342
|
>
|
|
343
343
|
Edit captions
|
|
344
344
|
</button>
|
|
@@ -110,7 +110,7 @@ export function PreviewPane({
|
|
|
110
110
|
ref={containerRef}
|
|
111
111
|
// Panel chrome (rounded border) is dropped in fullscreen so the preview
|
|
112
112
|
// fills the screen edge-to-edge.
|
|
113
|
-
className={`flex-1 min-h-0 flex flex-col overflow-hidden bg-neutral-950 ${
|
|
113
|
+
className={`flex-1 min-h-0 flex flex-col overflow-hidden bg-[var(--studio-preview-bg,var(--color-neutral-950))] ${
|
|
114
114
|
isFullscreen ? "" : "rounded-lg border border-neutral-800/50"
|
|
115
115
|
}`}
|
|
116
116
|
data-studio-fullscreen-target=""
|
|
@@ -139,7 +139,7 @@ export function PreviewPane({
|
|
|
139
139
|
onCompositionSizeChange={setPreviewCompositionSize}
|
|
140
140
|
/>
|
|
141
141
|
{previewDragOver && (
|
|
142
|
-
<div className="absolute inset-2 z-40 rounded-lg border-2 border-dashed border-studio-accent/50 bg-studio-accent/
|
|
142
|
+
<div className="absolute inset-2 z-40 rounded-lg border-2 border-dashed border-studio-accent/50 bg-studio-accent/4 pointer-events-none" />
|
|
143
143
|
)}
|
|
144
144
|
<AssetPreviewOverlay />
|
|
145
145
|
</div>
|
|
@@ -148,7 +148,7 @@ export function PreviewPane({
|
|
|
148
148
|
</div>
|
|
149
149
|
{/* Transport row: no own background or border — the controls sit flat on
|
|
150
150
|
the preview panel's surface (CapCut-style). */}
|
|
151
|
-
<div className="
|
|
151
|
+
<div className="shrink-0">
|
|
152
152
|
{!isFullscreen && compositionStack.length > 1 && (
|
|
153
153
|
<CompositionBreadcrumb stack={compositionStack} onNavigate={handleNavigateComposition} />
|
|
154
154
|
)}
|
|
@@ -266,7 +266,7 @@ export function TimelinePane({
|
|
|
266
266
|
{/* Timeline section — inner padding (not margin) keeps the divider's
|
|
267
267
|
height math exact while giving the panel a gap from the shell edges. */}
|
|
268
268
|
<div
|
|
269
|
-
className="relative flex flex-col
|
|
269
|
+
className="relative flex flex-col shrink-0 px-px pb-px"
|
|
270
270
|
style={{ height: timelineH }}
|
|
271
271
|
aria-disabled={timelineDisabled || undefined}
|
|
272
272
|
>
|
|
@@ -280,7 +280,7 @@ export function TimelinePane({
|
|
|
280
280
|
}
|
|
281
281
|
}}
|
|
282
282
|
>
|
|
283
|
-
<div className="
|
|
283
|
+
<div className="shrink-0">{timelineToolbar}</div>
|
|
284
284
|
<Timeline
|
|
285
285
|
sessionEpoch={timelineSessionEpoch}
|
|
286
286
|
onSeek={seek}
|
|
@@ -304,7 +304,7 @@ export function TimelinePane({
|
|
|
304
304
|
canPasteClip={canPasteClip}
|
|
305
305
|
/>
|
|
306
306
|
</div>
|
|
307
|
-
{timelineFooter && <div className="
|
|
307
|
+
{timelineFooter && <div className="shrink-0">{timelineFooter}</div>}
|
|
308
308
|
{timelineDisabled && (
|
|
309
309
|
<div
|
|
310
310
|
className="absolute inset-0 z-30 cursor-not-allowed bg-black/18 flex items-center justify-center"
|
|
@@ -79,7 +79,7 @@ export function TimelineResizeDivider({
|
|
|
79
79
|
(containerRef.current?.getBoundingClientRect().height ?? 600) - MIN_PREVIEW_H,
|
|
80
80
|
)}
|
|
81
81
|
tabIndex={0}
|
|
82
|
-
className="group relative h-[3px]
|
|
82
|
+
className="group relative h-[3px] shrink-0 cursor-row-resize z-10 outline-hidden focus-visible:bg-studio-accent/20"
|
|
83
83
|
style={{ touchAction: "none" }}
|
|
84
84
|
onPointerDown={handlePointerDown}
|
|
85
85
|
onPointerMove={handlePointerMove}
|
|
@@ -93,7 +93,7 @@ export function TimelineResizeDivider({
|
|
|
93
93
|
the 24px WCAG 2.2 (2.5.8) target because that is all the dead space
|
|
94
94
|
there is: 28px transport buttons sit above and 28px toolbar buttons
|
|
95
95
|
below, and silently stealing their clicks is the worse bug. */}
|
|
96
|
-
<div className="absolute inset-x-0
|
|
96
|
+
<div className="absolute inset-x-0 top-[-4px] h-[10px]" />
|
|
97
97
|
{/* Visible hairline — invisible at rest, subtle wash on hover/drag/focus */}
|
|
98
98
|
<div className="h-[3px] w-full bg-transparent transition-colors group-hover:bg-white/12 group-active:bg-white/18 group-focus-visible:bg-studio-accent/60" />
|
|
99
99
|
</div>
|
|
@@ -435,7 +435,7 @@ export function SlideshowPanel({ scenes, onPersist, onPersistNotes }: SlideshowP
|
|
|
435
435
|
type="button"
|
|
436
436
|
disabled={retrying}
|
|
437
437
|
onClick={handleRetryPersist}
|
|
438
|
-
className="px-2 py-0.5 text-[10px] rounded bg-red-600 text-white enabled:hover:bg-red-500 enabled:active:scale-[0.97] disabled:opacity-50 transition-colors"
|
|
438
|
+
className="px-2 py-0.5 text-[10px] rounded-sm bg-red-600 text-white enabled:hover:bg-red-500 enabled:active:scale-[0.97] disabled:opacity-50 transition-colors"
|
|
439
439
|
>
|
|
440
440
|
{retrying ? "Retrying…" : "Retry"}
|
|
441
441
|
</button>
|
|
@@ -447,7 +447,7 @@ export function SlideshowPanel({ scenes, onPersist, onPersistNotes }: SlideshowP
|
|
|
447
447
|
type="button"
|
|
448
448
|
onClick={handleUndo}
|
|
449
449
|
title="Undo last slideshow edit (⌘Z)"
|
|
450
|
-
className="px-2 py-0.5 text-[10px] rounded text-neutral-400 hover:text-neutral-200 hover:bg-neutral-800 active:scale-[0.97] transition-colors"
|
|
450
|
+
className="px-2 py-0.5 text-[10px] rounded-sm text-neutral-400 hover:text-neutral-200 hover:bg-neutral-800 active:scale-[0.97] transition-colors"
|
|
451
451
|
>
|
|
452
452
|
Undo ({undoDepth})
|
|
453
453
|
</button>
|