@hyperframes/studio 0.8.104 → 0.8.106
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-dl6K6ABp.js} +1 -1
- package/dist/assets/index-CMiYVGv5.js +399 -0
- package/dist/assets/{index-WV9LroAD.js → index-DKT5NF1Y.js} +1 -1
- package/dist/assets/index-lrT-gfHa.css +1 -0
- package/dist/assets/{index-BDK4rTMb.js → index-ooaZZhKf.js} +1 -1
- package/dist/{chunk-62DDWOSH.js → chunk-EEXSYY5F.js} +19 -18
- package/dist/chunk-EEXSYY5F.js.map +1 -0
- package/dist/{chunk-HIGHVWYD.js → chunk-ICTZJPQQ.js} +3 -2
- package/dist/chunk-ICTZJPQQ.js.map +1 -0
- package/dist/{chunk-6GAO75IU.js → chunk-NCUBAVGK.js} +10 -8
- package/dist/chunk-NCUBAVGK.js.map +1 -0
- package/dist/index.css +6 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +15 -4
- package/dist/index.html +11 -2
- package/dist/index.js +10391 -9836
- 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 +9 -9
- 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/captions/keyboard.test.ts +6 -0
- package/src/captions/keyboard.ts +2 -1
- 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 +35 -13
- package/src/components/StudioHeader.tsx +6 -2
- package/src/components/StudioToast.tsx +7 -16
- package/src/components/ThemeToggle.tsx +99 -0
- 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/DomEditOverlay.tsx +37 -5
- 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/SourceEditor.tsx +2 -6
- 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/domEditOverlayStartGesture.ts +127 -115
- package/src/components/editor/easeCurveSvg.tsx +1 -1
- package/src/components/editor/gestureHoldsPreviewReload.test.tsx +275 -0
- package/src/components/editor/manualEditsDom.ts +36 -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/marqueeCommit.test.tsx +69 -1
- package/src/components/editor/marqueeCommit.ts +6 -1
- package/src/components/editor/overlayFrameLoop.test.ts +27 -0
- package/src/components/editor/overlayFrameLoop.ts +21 -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.test.tsx +80 -3
- package/src/components/editor/propertyPanelFont.tsx +71 -48
- 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/propertyPanelHelpers.ts +1 -2
- 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/editor/studioCodeTheme.test.ts +20 -0
- package/src/components/editor/studioCodeTheme.ts +99 -0
- package/src/components/editor/useDomEditNudge.test.tsx +31 -0
- package/src/components/editor/useDomEditNudge.ts +2 -2
- package/src/components/editor/useInlineTextEditing.tsx +1 -1
- package/src/components/editor/useMotionPathData.test.tsx +64 -0
- package/src/components/editor/useMotionPathData.ts +5 -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/appHotkeysDispatch.ts +2 -1
- package/src/hooks/elementOffsetStager.ts +8 -7
- package/src/hooks/previewPromotionSelection.test.tsx +180 -0
- package/src/hooks/useAppHotkeys.test.ts +32 -0
- package/src/hooks/useAppHotkeys.ts +1 -2
- package/src/hooks/useClipboard.source.test.ts +141 -23
- package/src/hooks/useClipboard.ts +154 -123
- package/src/hooks/useDomEditCommits.ts +0 -3
- package/src/hooks/useDomEditSession.test.tsx +76 -72
- package/src/hooks/useDomEditSession.ts +7 -43
- 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/hooks/useGsapTweenCache.ts +1 -2
- package/src/hooks/useInlineTextEdit.ts +16 -2
- package/src/hooks/useLivePreviewIframe.ts +18 -0
- package/src/hooks/usePreviewPersistence.history.test.tsx +47 -0
- package/src/hooks/usePreviewPersistence.ts +2 -5
- package/src/hooks/useRenderClipContent.ts +3 -3
- package/src/index.ts +2 -0
- package/src/livePreviewIframeExport.test.tsx +33 -0
- package/src/main.tsx +4 -1
- package/src/player/components/AudioWaveform.test.tsx +36 -15
- package/src/player/components/AudioWaveform.tsx +24 -7
- 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.test.tsx +38 -0
- package/src/player/components/TimelineClip.tsx +16 -8
- package/src/player/components/TimelineClipFades.test.tsx +376 -74
- package/src/player/components/TimelineClipFades.tsx +368 -137
- 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/components/useTimelineKeyboardActor.ts +1 -1
- package/src/player/hooks/timelinePlayerTestHarness.ts +27 -0
- package/src/player/hooks/useShadowPreviewReload.ts +72 -5
- package/src/player/lib/automationStoreSync.test.ts +18 -1
- package/src/player/lib/automationStoreSync.ts +29 -17
- 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/player/sceneSwap.ts +18 -0
- package/src/styles/components.css +26 -0
- package/src/styles/hex-baseline.json +7 -19
- package/src/styles/studio.css +4 -7
- package/src/styles/tailwind-preset.shared.js +2 -1
- package/src/styles/theme.css +214 -147
- package/src/styles/theme.test.ts +13 -9
- package/src/utils/blockCategories.ts +41 -9
- package/src/utils/clipboardPayload.ts +12 -1
- package/src/utils/studioPendingEdits.ts +21 -0
- package/src/utils/studioTheme.test.tsx +141 -0
- package/src/utils/studioTheme.ts +34 -0
- package/src/utils/studioUiPreferences.ts +3 -0
- package/src/utils/typingTarget.test.ts +8 -1
- package/src/utils/typingTarget.ts +5 -0
- package/dist/assets/index-CQN3M56N.css +0 -1
- package/dist/assets/index-De2aO4XM.js +0 -399
- package/dist/chunk-62DDWOSH.js.map +0 -1
- package/dist/chunk-6GAO75IU.js.map +0 -1
- package/dist/chunk-HIGHVWYD.js.map +0 -1
- package/src/hooks/gsapTargetCache.ts +0 -62
package/dist/ui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
HyperframesLogo
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EEXSYY5F.js";
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
ContextMenu,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
fieldBase,
|
|
31
31
|
fieldText,
|
|
32
32
|
popupSurface
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-NCUBAVGK.js";
|
|
34
34
|
import "./chunk-PZ5AY32C.js";
|
|
35
35
|
export {
|
|
36
36
|
Button,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.106",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"@codemirror/language": "^6.12.2",
|
|
51
51
|
"@codemirror/search": "^6.6.0",
|
|
52
52
|
"@codemirror/state": "^6.6.0",
|
|
53
|
-
"@codemirror/theme-one-dark": "^6.1.2",
|
|
54
53
|
"@codemirror/view": "6.40.0",
|
|
54
|
+
"@lezer/highlight": "^1.2.3",
|
|
55
55
|
"@mcp-b/global": "^5.0.1",
|
|
56
56
|
"@phosphor-icons/react": "^2.1.10",
|
|
57
57
|
"@tanstack/react-virtual": "^3.14.13",
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
"dockview-react": "8.3.1",
|
|
61
61
|
"gsap": "^3.13.0",
|
|
62
62
|
"mediabunny": "^1.45.3",
|
|
63
|
-
"@hyperframes/core": "0.8.
|
|
64
|
-
"@hyperframes/parsers": "0.8.
|
|
65
|
-
"@hyperframes/
|
|
66
|
-
"@hyperframes/
|
|
67
|
-
"@hyperframes/shader-transitions": "0.8.
|
|
68
|
-
"@hyperframes/studio-server": "0.8.
|
|
63
|
+
"@hyperframes/core": "0.8.106",
|
|
64
|
+
"@hyperframes/parsers": "0.8.106",
|
|
65
|
+
"@hyperframes/sdk": "0.8.106",
|
|
66
|
+
"@hyperframes/player": "0.8.106",
|
|
67
|
+
"@hyperframes/shader-transitions": "0.8.106",
|
|
68
|
+
"@hyperframes/studio-server": "0.8.106"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@tailwindcss/vite": "^4.3.3",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"vite": "^6.4.2",
|
|
82
82
|
"vitest": "^4.1.11",
|
|
83
83
|
"zustand": "^5.0.0",
|
|
84
|
-
"@hyperframes/producer": "0.8.
|
|
84
|
+
"@hyperframes/producer": "0.8.106"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"react": "19",
|
|
@@ -250,7 +250,7 @@ export const CaptionAnimationPanel = memo(function CaptionAnimationPanel() {
|
|
|
250
250
|
<div className="flex flex-col h-full min-h-0">
|
|
251
251
|
{gated && (
|
|
252
252
|
<div className="shrink-0 mx-3 mt-2 px-2 py-1.5 rounded-sm border border-amber-500/30 bg-amber-500/10">
|
|
253
|
-
<p className="text-2xs text-
|
|
253
|
+
<p className="text-2xs text-warning-ink leading-snug">
|
|
254
254
|
Animation editing isn't applied to playback or saved yet, so these controls are
|
|
255
255
|
disabled.
|
|
256
256
|
</p>
|
|
@@ -292,7 +292,7 @@ export const CaptionAnimationPanel = memo(function CaptionAnimationPanel() {
|
|
|
292
292
|
onClick={handleApplyToAll}
|
|
293
293
|
disabled={gated}
|
|
294
294
|
title={gated ? "Disabled until animation editing is applied to playback" : undefined}
|
|
295
|
-
className="w-full py-1.5 rounded-sm border border-neutral-700 text-2xs text-neutral-300 hover:border-studio-accent/50 hover:text-
|
|
295
|
+
className="w-full py-1.5 rounded-sm border border-neutral-700 text-2xs text-neutral-300 hover:border-studio-accent/50 hover:text-accent-ink transition-colors disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:border-neutral-700 disabled:hover:text-neutral-300"
|
|
296
296
|
>
|
|
297
297
|
Apply to all groups
|
|
298
298
|
</button>
|
|
@@ -227,7 +227,7 @@ export const CaptionPropertyPanel = memo(function CaptionPropertyPanel({
|
|
|
227
227
|
className={[
|
|
228
228
|
"flex-1 py-0.5 rounded-sm text-2xs font-medium transition-colors",
|
|
229
229
|
activeTab === "style"
|
|
230
|
-
? "bg-studio-accent/20 text-
|
|
230
|
+
? "bg-studio-accent/20 text-accent-ink border border-studio-accent/50"
|
|
231
231
|
: "text-neutral-500 border border-neutral-800 hover:text-neutral-300 hover:border-neutral-600",
|
|
232
232
|
].join(" ")}
|
|
233
233
|
>
|
|
@@ -241,7 +241,7 @@ export const CaptionPropertyPanel = memo(function CaptionPropertyPanel({
|
|
|
241
241
|
className={[
|
|
242
242
|
"flex-1 py-0.5 rounded-sm text-2xs font-medium transition-colors",
|
|
243
243
|
activeTab === "animation"
|
|
244
|
-
? "bg-studio-accent/20 text-
|
|
244
|
+
? "bg-studio-accent/20 text-accent-ink border border-studio-accent/50"
|
|
245
245
|
: "text-neutral-500 border border-neutral-800 hover:text-neutral-300 hover:border-neutral-600",
|
|
246
246
|
].join(" ")}
|
|
247
247
|
>
|
|
@@ -109,8 +109,8 @@ export const CaptionTimeline = memo(function CaptionTimeline({
|
|
|
109
109
|
tabIndex={0}
|
|
110
110
|
aria-label={`Caption word "${seg.text}"`}
|
|
111
111
|
aria-pressed={isSelected}
|
|
112
|
-
className={`absolute top-1 bottom-1 rounded flex items-center overflow-hidden cursor-pointer focus-visible:ring-1 focus-visible:ring-
|
|
113
|
-
isSelected ? " ring-1 ring-
|
|
112
|
+
className={`absolute top-1 bottom-1 rounded flex items-center overflow-hidden cursor-pointer focus-visible:ring-1 focus-visible:ring-text-0 outline-hidden${
|
|
113
|
+
isSelected ? " ring-1 ring-text-0/50 z-10" : ""
|
|
114
114
|
}`}
|
|
115
115
|
style={{
|
|
116
116
|
left,
|
|
@@ -135,7 +135,7 @@ export const CaptionTimeline = memo(function CaptionTimeline({
|
|
|
135
135
|
|
|
136
136
|
{/* Playhead — correlates blocks with the current frame */}
|
|
137
137
|
<div
|
|
138
|
-
className="absolute top-0 bottom-0 w-px bg-
|
|
138
|
+
className="absolute top-0 bottom-0 w-px bg-text-0/70 pointer-events-none z-20"
|
|
139
139
|
style={{ left: playheadLeft }}
|
|
140
140
|
aria-hidden="true"
|
|
141
141
|
/>
|
|
@@ -38,6 +38,12 @@ describe("shouldHandleCaptionNudgeKey", () => {
|
|
|
38
38
|
expect(shouldHandleCaptionNudgeKey(mockKeyboardEvent("KeyL"))).toBe(false);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
it("ignores arrows a focused slider owns", () => {
|
|
42
|
+
const slider = document.createElement("div");
|
|
43
|
+
slider.setAttribute("role", "slider");
|
|
44
|
+
expect(shouldHandleCaptionNudgeKey(mockKeyboardEvent("ArrowUp"), slider)).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
41
47
|
it("ignores arrows when the event target is an editable element", () => {
|
|
42
48
|
const input = document.createElement("input");
|
|
43
49
|
const textarea = document.createElement("textarea");
|
package/src/captions/keyboard.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isEditableTarget } from "../utils/timelineDiscovery";
|
|
2
|
+
import { ownsPlainKeys } from "../utils/typingTarget";
|
|
2
3
|
|
|
3
4
|
const CAPTION_NUDGE_KEYS = new Set(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"]);
|
|
4
5
|
|
|
@@ -9,7 +10,7 @@ export function shouldHandleCaptionNudgeKey(
|
|
|
9
10
|
target?: EventTarget | null,
|
|
10
11
|
): boolean {
|
|
11
12
|
if (event.metaKey || event.ctrlKey || event.altKey) return false;
|
|
12
|
-
if (target != null &&
|
|
13
|
+
if (target != null && ownsPlainKeys(target)) return false;
|
|
13
14
|
return CAPTION_NUDGE_KEYS.has(event.key);
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -138,7 +138,7 @@ export function AskAgentModal({
|
|
|
138
138
|
{navigator.platform.includes("Mac") ? "⌘" : "Ctrl"}+Enter to copy
|
|
139
139
|
</span>
|
|
140
140
|
<button
|
|
141
|
-
className="px-4 py-1.5 rounded-lg bg-
|
|
141
|
+
className="px-4 py-1.5 rounded-lg bg-accent text-xs font-medium text-on-accent hover:bg-accent-hover disabled:opacity-40 disabled:cursor-not-allowed"
|
|
142
142
|
disabled={!value.trim()}
|
|
143
143
|
onClick={handleSubmit}
|
|
144
144
|
>
|
|
@@ -75,11 +75,11 @@ export function LintModal({
|
|
|
75
75
|
<div className="flex items-center gap-3">
|
|
76
76
|
{hasIssues ? (
|
|
77
77
|
<div className="w-8 h-8 rounded-full bg-red-500/10 flex items-center justify-center">
|
|
78
|
-
<WarningIcon size={18} className="text-
|
|
78
|
+
<WarningIcon size={18} className="text-danger-ink" weight="fill" />
|
|
79
79
|
</div>
|
|
80
80
|
) : (
|
|
81
81
|
<div className="w-8 h-8 rounded-full bg-studio-accent/10 flex items-center justify-center">
|
|
82
|
-
<CheckCircleIcon size={18} className="text-
|
|
82
|
+
<CheckCircleIcon size={18} className="text-accent-ink" weight="fill" />
|
|
83
83
|
</div>
|
|
84
84
|
)}
|
|
85
85
|
<div>
|
|
@@ -107,10 +107,10 @@ export function LintModal({
|
|
|
107
107
|
onClick={handleCopyToAgent}
|
|
108
108
|
className={`px-3 py-1 text-xs font-medium rounded-lg transition-colors active:scale-[0.98] ${
|
|
109
109
|
copied
|
|
110
|
-
? "bg-
|
|
110
|
+
? "bg-accent text-on-accent"
|
|
111
111
|
: copyFailed
|
|
112
|
-
? "bg-
|
|
113
|
-
: "bg-
|
|
112
|
+
? "bg-danger text-on-danger"
|
|
113
|
+
: "bg-accent hover:bg-accent-hover text-on-accent"
|
|
114
114
|
}`}
|
|
115
115
|
>
|
|
116
116
|
{copied
|
|
@@ -128,41 +128,41 @@ export function LintModal({
|
|
|
128
128
|
</div>
|
|
129
129
|
)}
|
|
130
130
|
{errors.map((f, i) => (
|
|
131
|
-
<
|
|
132
|
-
<div className="flex items-start gap-2">
|
|
133
|
-
<WarningIcon size={14} className="text-red-400 shrink-0 mt-0.5" weight="fill" />
|
|
134
|
-
<div className="min-w-0">
|
|
135
|
-
<p className="text-sm text-neutral-200">{f.message}</p>
|
|
136
|
-
{f.file && <p className="text-xs text-neutral-600 font-mono mt-0.5">{f.file}</p>}
|
|
137
|
-
{f.fixHint && (
|
|
138
|
-
<div className="flex items-start gap-1 mt-1.5">
|
|
139
|
-
<CaretRightIcon size={10} className="text-studio-accent shrink-0 mt-0.5" />
|
|
140
|
-
<p className="text-xs text-studio-accent">{f.fixHint}</p>
|
|
141
|
-
</div>
|
|
142
|
-
)}
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
131
|
+
<LintFindingRow key={`e-${i}`} finding={f} tone="error" />
|
|
146
132
|
))}
|
|
147
133
|
{warnings.map((f, i) => (
|
|
148
|
-
<
|
|
149
|
-
<div className="flex items-start gap-2">
|
|
150
|
-
<WarningIcon size={14} className="text-amber-400 shrink-0 mt-0.5" />
|
|
151
|
-
<div className="min-w-0">
|
|
152
|
-
<p className="text-sm text-neutral-300">{f.message}</p>
|
|
153
|
-
{f.file && <p className="text-xs text-neutral-600 font-mono mt-0.5">{f.file}</p>}
|
|
154
|
-
{f.fixHint && (
|
|
155
|
-
<div className="flex items-start gap-1 mt-1.5">
|
|
156
|
-
<CaretRightIcon size={10} className="text-studio-accent shrink-0 mt-0.5" />
|
|
157
|
-
<p className="text-xs text-studio-accent">{f.fixHint}</p>
|
|
158
|
-
</div>
|
|
159
|
-
)}
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
134
|
+
<LintFindingRow key={`w-${i}`} finding={f} tone="warning" />
|
|
163
135
|
))}
|
|
164
136
|
</div>
|
|
165
137
|
</div>
|
|
166
138
|
</div>
|
|
167
139
|
);
|
|
168
140
|
}
|
|
141
|
+
|
|
142
|
+
function LintFindingRow({ finding, tone }: { finding: LintFinding; tone: "error" | "warning" }) {
|
|
143
|
+
return (
|
|
144
|
+
<div className="py-3 border-b border-border last:border-0">
|
|
145
|
+
<div className="flex items-start gap-2">
|
|
146
|
+
<WarningIcon
|
|
147
|
+
size={14}
|
|
148
|
+
className={`${tone === "error" ? "text-danger-ink" : "text-warning-ink"} shrink-0 mt-0.5`}
|
|
149
|
+
weight={tone === "error" ? "fill" : undefined}
|
|
150
|
+
/>
|
|
151
|
+
<div className="min-w-0">
|
|
152
|
+
<p className={`text-sm ${tone === "error" ? "text-text-0" : "text-text-2"}`}>
|
|
153
|
+
{finding.message}
|
|
154
|
+
</p>
|
|
155
|
+
{finding.file && (
|
|
156
|
+
<p className="text-xs text-text-muted font-mono mt-0.5">{finding.file}</p>
|
|
157
|
+
)}
|
|
158
|
+
{finding.fixHint && (
|
|
159
|
+
<div className="flex items-start gap-1 mt-1.5">
|
|
160
|
+
<CaretRightIcon size={10} className="text-accent-ink shrink-0 mt-0.5" />
|
|
161
|
+
<p className="text-xs text-accent-ink">{finding.fixHint}</p>
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -39,7 +39,7 @@ export class StudioErrorBoundary extends Component<Props, State> {
|
|
|
39
39
|
<div className="mt-2 flex items-center gap-2">
|
|
40
40
|
<button
|
|
41
41
|
onClick={() => this.setState({ error: null })}
|
|
42
|
-
className="rounded-md bg-
|
|
42
|
+
className="rounded-md bg-accent px-5 py-2 text-sm font-medium text-on-accent transition-colors hover:bg-accent-hover active:scale-[0.98]"
|
|
43
43
|
>
|
|
44
44
|
Try again
|
|
45
45
|
</button>
|
|
@@ -42,6 +42,7 @@ vi.mock("../contexts/PanelLayoutContext", () => ({
|
|
|
42
42
|
vi.mock("../utils/studioTelemetry", () => ({ trackStudioEvent: vi.fn() }));
|
|
43
43
|
|
|
44
44
|
const { StudioHeader } = await import("./StudioHeader");
|
|
45
|
+
const { ShowThemeToggle } = await import("./ThemeToggle");
|
|
45
46
|
|
|
46
47
|
let mounted: { root: Root; host: HTMLElement } | null = null;
|
|
47
48
|
|
|
@@ -54,6 +55,7 @@ beforeEach(() => {
|
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
afterEach(() => {
|
|
58
|
+
delete document.documentElement.dataset.theme;
|
|
57
59
|
if (!mounted) return;
|
|
58
60
|
const { root, host } = mounted;
|
|
59
61
|
mounted = null;
|
|
@@ -61,21 +63,30 @@ afterEach(() => {
|
|
|
61
63
|
host.remove();
|
|
62
64
|
});
|
|
63
65
|
|
|
64
|
-
function mount(
|
|
66
|
+
function mount(
|
|
67
|
+
props: { inspectorButtonActive?: boolean; themeToggle?: boolean } = {},
|
|
68
|
+
): HTMLElement {
|
|
65
69
|
const host = document.createElement("div");
|
|
66
70
|
document.body.append(host);
|
|
67
71
|
const root = createRoot(host);
|
|
68
72
|
mounted = { root, host };
|
|
73
|
+
const header = (
|
|
74
|
+
<StudioHeader
|
|
75
|
+
captureFrameHref="blob:frame"
|
|
76
|
+
captureFrameFilename="frame.png"
|
|
77
|
+
handleCaptureFrameClick={vi.fn()}
|
|
78
|
+
refreshCaptureFrameTime={vi.fn()}
|
|
79
|
+
inspectorButtonActive={props.inspectorButtonActive ?? false}
|
|
80
|
+
inspectorPanelActive={false}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
69
83
|
act(() =>
|
|
70
84
|
root.render(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
inspectorButtonActive={props.inspectorButtonActive ?? false}
|
|
77
|
-
inspectorPanelActive={false}
|
|
78
|
-
/>,
|
|
85
|
+
props.themeToggle ? (
|
|
86
|
+
<ShowThemeToggle.Provider value>{header}</ShowThemeToggle.Provider>
|
|
87
|
+
) : (
|
|
88
|
+
header
|
|
89
|
+
),
|
|
79
90
|
),
|
|
80
91
|
);
|
|
81
92
|
return host;
|
|
@@ -139,16 +150,16 @@ it("shows Inspector pressed and filled only when on", () => {
|
|
|
139
150
|
const host = mount({ inspectorButtonActive: true });
|
|
140
151
|
const on = query(host, '[aria-label="Inspector"]');
|
|
141
152
|
expect(on.getAttribute("aria-pressed")).toBe("true");
|
|
142
|
-
expect(hasToken(on.className, "text-accent")).toBe(true);
|
|
143
|
-
expect(hasToken(on.className, "bg-
|
|
153
|
+
expect(hasToken(on.className, "text-accent-ink")).toBe(true);
|
|
154
|
+
expect(hasToken(on.className, "bg-on")).toBe(true);
|
|
144
155
|
act(() => mounted?.root.unmount());
|
|
145
156
|
mounted?.host.remove();
|
|
146
157
|
mounted = null;
|
|
147
158
|
|
|
148
159
|
const off = query(mount(), '[aria-label="Inspector"]');
|
|
149
160
|
expect(off.getAttribute("aria-pressed")).toBe("false");
|
|
150
|
-
expect(hasToken(off.className, "text-accent")).toBe(false);
|
|
151
|
-
expect(hasToken(off.className, "bg-
|
|
161
|
+
expect(hasToken(off.className, "text-accent-ink")).toBe(false);
|
|
162
|
+
expect(hasToken(off.className, "bg-on")).toBe(false);
|
|
152
163
|
});
|
|
153
164
|
|
|
154
165
|
it("keeps Capture a real download link rather than a button", () => {
|
|
@@ -179,3 +190,14 @@ it("classifies the new header controls for the hotkey filters as the old ones we
|
|
|
179
190
|
expect(shouldIgnorePlaybackShortcutTarget(el), el.tagName).toBe(true);
|
|
180
191
|
}
|
|
181
192
|
});
|
|
193
|
+
|
|
194
|
+
it("shows no theme toggle and leaves the host's data-theme alone when embedded", () => {
|
|
195
|
+
document.documentElement.dataset.theme = "host";
|
|
196
|
+
const host = mount();
|
|
197
|
+
expect(host.querySelector('[aria-label^="Switch to"]')).toBeNull();
|
|
198
|
+
expect(document.documentElement.dataset.theme).toBe("host");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("shows the theme toggle in Studio's own app", () => {
|
|
202
|
+
expect(mount({ themeToggle: true }).querySelector('[aria-label^="Switch to"]')).not.toBeNull();
|
|
203
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { useContext, type MouseEvent } from "react";
|
|
2
2
|
import { Camera } from "../icons/SystemIcons";
|
|
3
3
|
import { useStudioShellContext } from "../contexts/StudioContext";
|
|
4
4
|
import { usePanelLayoutContext } from "../contexts/PanelLayoutContext";
|
|
@@ -7,6 +7,7 @@ import { Button, buttonBase, buttonSizes, buttonVariants, cn, Tooltip } from "./
|
|
|
7
7
|
import { Dock } from "./dock/Dock";
|
|
8
8
|
import { InspectorIcon } from "./icons/InspectorIcon";
|
|
9
9
|
import { HyperframesLogo } from "./ui/HyperframesLogo";
|
|
10
|
+
import { ShowThemeToggle, ThemeToggle } from "./ThemeToggle";
|
|
10
11
|
|
|
11
12
|
export interface StudioHeaderProps {
|
|
12
13
|
captureFrameHref: string;
|
|
@@ -44,6 +45,7 @@ export function StudioHeader({
|
|
|
44
45
|
inspectorPanelActive,
|
|
45
46
|
onExport,
|
|
46
47
|
}: StudioHeaderProps) {
|
|
48
|
+
const showThemeToggle = useContext(ShowThemeToggle);
|
|
47
49
|
const { projectId, renderQueue } = useStudioShellContext();
|
|
48
50
|
const { rightCollapsed, setRightCollapsed, setRightPanelTab } = usePanelLayoutContext();
|
|
49
51
|
const isRendering = renderQueue.isRendering;
|
|
@@ -126,7 +128,8 @@ export function StudioHeader({
|
|
|
126
128
|
aria-pressed={inspectorButtonActive}
|
|
127
129
|
className={cn(
|
|
128
130
|
"h-full rounded-none",
|
|
129
|
-
inspectorButtonActive &&
|
|
131
|
+
inspectorButtonActive &&
|
|
132
|
+
"bg-on text-accent-ink enabled:hover:bg-on-hover enabled:hover:text-accent-ink",
|
|
130
133
|
)}
|
|
131
134
|
icon={<InspectorIcon size={16} />}
|
|
132
135
|
onClick={() => {
|
|
@@ -146,6 +149,7 @@ export function StudioHeader({
|
|
|
146
149
|
</Button>
|
|
147
150
|
</Tooltip>
|
|
148
151
|
</div>
|
|
152
|
+
{showThemeToggle && <ThemeToggle />}
|
|
149
153
|
<Dock.WindowMenu />
|
|
150
154
|
<Tooltip
|
|
151
155
|
label={
|
|
@@ -14,23 +14,10 @@ export function StudioToast({ message, tone, leaving, onDismiss }: StudioToastPr
|
|
|
14
14
|
className={`motion-reduce:animate-none ${leaving ? "hf-toast-exit" : "hf-toast-enter"}`}
|
|
15
15
|
>
|
|
16
16
|
<div
|
|
17
|
-
className=
|
|
18
|
-
style={{
|
|
19
|
-
background: isError
|
|
20
|
-
? "linear-gradient(135deg, rgba(127,29,29,0.55), rgba(80,10,10,0.45))"
|
|
21
|
-
: "linear-gradient(135deg, rgba(38,38,38,0.55), rgba(23,23,23,0.45))",
|
|
22
|
-
backdropFilter: "blur(16px) saturate(1.6)",
|
|
23
|
-
WebkitBackdropFilter: "blur(16px) saturate(1.6)",
|
|
24
|
-
border: `1px solid ${isError ? "rgba(239,68,68,0.18)" : "rgba(255,255,255,0.08)"}`,
|
|
25
|
-
boxShadow: [
|
|
26
|
-
"0 8px 32px rgba(0,0,0,0.35)",
|
|
27
|
-
`inset 0 1px 0 ${isError ? "rgba(239,68,68,0.12)" : "rgba(255,255,255,0.06)"}`,
|
|
28
|
-
`inset 0 -1px 0 rgba(0,0,0,0.15)`,
|
|
29
|
-
].join(", "),
|
|
30
|
-
}}
|
|
17
|
+
className={`relative flex max-w-[min(420px,calc(100vw-48px))] items-center gap-3 overflow-hidden rounded-2xl py-3 pl-4 pr-2 text-[12px] ${toastSurface(isError)}`}
|
|
31
18
|
>
|
|
32
19
|
<span
|
|
33
|
-
className={`min-w-0 wrap-break-word leading-5 ${isError ? "text-
|
|
20
|
+
className={`min-w-0 wrap-break-word leading-5 ${isError ? "text-danger-ink" : "text-text-0"}`}
|
|
34
21
|
>
|
|
35
22
|
{message}
|
|
36
23
|
</span>
|
|
@@ -38,7 +25,7 @@ export function StudioToast({ message, tone, leaving, onDismiss }: StudioToastPr
|
|
|
38
25
|
<button
|
|
39
26
|
type="button"
|
|
40
27
|
onClick={onDismiss}
|
|
41
|
-
className="flex h-5 w-5 shrink-0 items-center justify-center rounded-md text-neutral-500 transition-colors hover:bg-
|
|
28
|
+
className="flex h-5 w-5 shrink-0 items-center justify-center rounded-md text-neutral-500 transition-colors hover:bg-hover hover:text-neutral-300"
|
|
42
29
|
aria-label="Dismiss"
|
|
43
30
|
>
|
|
44
31
|
<svg
|
|
@@ -57,3 +44,7 @@ export function StudioToast({ message, tone, leaving, onDismiss }: StudioToastPr
|
|
|
57
44
|
</div>
|
|
58
45
|
);
|
|
59
46
|
}
|
|
47
|
+
|
|
48
|
+
export function toastSurface(isError: boolean): string {
|
|
49
|
+
return `border shadow-popover backdrop-blur-md ${isError ? "border-danger/40 bg-raised" : "border-border bg-raised/95"}`;
|
|
50
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
import { setStudioTheme, useShownStudioTheme } from "../utils/studioTheme";
|
|
3
|
+
import { IconButton, Tooltip } from "./ui";
|
|
4
|
+
|
|
5
|
+
// hyperframes.dev's theme toggle: the same sun and moon, revealing the new theme in a circle from
|
|
6
|
+
// the button. A page that paints no frame aborts the transition; the theme is applied by then.
|
|
7
|
+
let latest: ViewTransition | null = null;
|
|
8
|
+
|
|
9
|
+
function reveal(transition: ViewTransition, at: string, radiusPct: number) {
|
|
10
|
+
latest = transition;
|
|
11
|
+
const done = () => {
|
|
12
|
+
if (latest === transition) delete document.documentElement.dataset.themeReveal;
|
|
13
|
+
};
|
|
14
|
+
void transition.finished.then(done, done);
|
|
15
|
+
void transition.ready.then(
|
|
16
|
+
() => {
|
|
17
|
+
document.documentElement.animate(
|
|
18
|
+
{ clipPath: [`circle(0% ${at})`, `circle(${radiusPct}% ${at})`] },
|
|
19
|
+
{ duration: 500, easing: "ease-in-out", pseudoElement: "::view-transition-new(root)" },
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
() => {},
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function origin(e: React.MouseEvent<HTMLButtonElement>): [number, number] {
|
|
27
|
+
if (e.detail > 0) return [e.clientX, e.clientY];
|
|
28
|
+
const box = e.currentTarget.getBoundingClientRect();
|
|
29
|
+
return [box.left + box.width / 2, box.top + box.height / 2];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** On only in Studio's own app; a host that embeds Studio owns its theme. */
|
|
33
|
+
export const ShowThemeToggle = createContext(false);
|
|
34
|
+
|
|
35
|
+
function cannotReveal(): boolean {
|
|
36
|
+
return (
|
|
37
|
+
!document.startViewTransition ||
|
|
38
|
+
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const iconProps = {
|
|
43
|
+
width: "14",
|
|
44
|
+
height: "14",
|
|
45
|
+
viewBox: "0 0 24 24",
|
|
46
|
+
fill: "none",
|
|
47
|
+
stroke: "currentColor",
|
|
48
|
+
strokeWidth: "2",
|
|
49
|
+
strokeLinecap: "round",
|
|
50
|
+
strokeLinejoin: "round",
|
|
51
|
+
"aria-hidden": true,
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
export function ThemeToggle() {
|
|
55
|
+
const isDark = useShownStudioTheme() === "dark";
|
|
56
|
+
const label = `Switch to ${isDark ? "light" : "dark"} theme`;
|
|
57
|
+
|
|
58
|
+
function toggle(e: React.MouseEvent<HTMLButtonElement>) {
|
|
59
|
+
const apply = () => setStudioTheme(isDark ? "light" : "dark");
|
|
60
|
+
if (cannotReveal()) return apply();
|
|
61
|
+
const [x, y] = origin(e);
|
|
62
|
+
const w = window.innerWidth;
|
|
63
|
+
const h = window.innerHeight;
|
|
64
|
+
const maxRadius = Math.hypot(Math.max(x, w - x), Math.max(y, h - y));
|
|
65
|
+
const at = `at ${(x / w) * 100}% ${(y / h) * 100}%`;
|
|
66
|
+
const radiusPct = (maxRadius / (Math.hypot(w, h) / Math.SQRT2)) * 100;
|
|
67
|
+
// The switch stays synchronous inside the callback: rendering is paused during it.
|
|
68
|
+
document.documentElement.dataset.themeReveal = "";
|
|
69
|
+
reveal(document.startViewTransition(apply), at, radiusPct);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Tooltip label={label} side="bottom">
|
|
74
|
+
<IconButton
|
|
75
|
+
onClick={toggle}
|
|
76
|
+
aria-label={label}
|
|
77
|
+
icon={
|
|
78
|
+
isDark ? (
|
|
79
|
+
<svg {...iconProps}>
|
|
80
|
+
<circle cx="12" cy="12" r="5" />
|
|
81
|
+
<line x1="12" y1="1" x2="12" y2="3" />
|
|
82
|
+
<line x1="12" y1="21" x2="12" y2="23" />
|
|
83
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
|
84
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
|
85
|
+
<line x1="1" y1="12" x2="3" y2="12" />
|
|
86
|
+
<line x1="21" y1="12" x2="23" y2="12" />
|
|
87
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
|
88
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
|
89
|
+
</svg>
|
|
90
|
+
) : (
|
|
91
|
+
<svg {...iconProps}>
|
|
92
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
93
|
+
</svg>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
/>
|
|
97
|
+
</Tooltip>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -81,7 +81,7 @@ export function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
|
|
|
81
81
|
shortcut={row.shortcut}
|
|
82
82
|
onClick={row.run}
|
|
83
83
|
data-active={row.active || undefined}
|
|
84
|
-
className={row.active ? "bg-
|
|
84
|
+
className={row.active ? "bg-press text-text-0" : undefined}
|
|
85
85
|
>
|
|
86
86
|
<span className="flex items-center gap-2">
|
|
87
87
|
{row.icon}
|
|
@@ -307,11 +307,11 @@ export function TimelineToolbar({
|
|
|
307
307
|
className={
|
|
308
308
|
!onToggleKeyframe
|
|
309
309
|
? flatDisabled
|
|
310
|
-
: `${flatBtn} active:scale-[0.98] hover:bg-
|
|
310
|
+
: `${flatBtn} active:scale-[0.98] hover:bg-hover ${
|
|
311
311
|
keyframeState === "active"
|
|
312
|
-
? "text-
|
|
312
|
+
? "text-accent-ink"
|
|
313
313
|
: keyframeState === "inactive"
|
|
314
|
-
? "text-neutral-400 hover:text-
|
|
314
|
+
? "text-neutral-400 hover:text-accent-ink"
|
|
315
315
|
: "text-neutral-600 hover:text-neutral-400"
|
|
316
316
|
}`
|
|
317
317
|
}
|
|
@@ -342,9 +342,9 @@ export function TimelineToolbar({
|
|
|
342
342
|
onClick={() => setAutoKeyframeEnabled(!autoKeyframeEnabled)}
|
|
343
343
|
aria-label="Auto-record manual edits as keyframes"
|
|
344
344
|
aria-pressed={autoKeyframeEnabled}
|
|
345
|
-
className={`${flatBtn} active:scale-[0.98] hover:bg-
|
|
345
|
+
className={`${flatBtn} active:scale-[0.98] hover:bg-hover ${
|
|
346
346
|
autoKeyframeEnabled
|
|
347
|
-
? "text-
|
|
347
|
+
? "text-danger-ink"
|
|
348
348
|
: "text-neutral-600 hover:text-neutral-400"
|
|
349
349
|
}`}
|
|
350
350
|
>
|
|
@@ -442,7 +442,7 @@ export function TimelineToolbar({
|
|
|
442
442
|
}}
|
|
443
443
|
className={
|
|
444
444
|
canAdd
|
|
445
|
-
? `${flatBtn} text-
|
|
445
|
+
? `${flatBtn} text-text-2 hover:bg-hover hover:text-accent-ink active:scale-[0.98]`
|
|
446
446
|
: flatDisabled
|
|
447
447
|
}
|
|
448
448
|
>
|
|
@@ -479,8 +479,8 @@ export function TimelineToolbar({
|
|
|
479
479
|
onClick={() => setThumbnailMode(thumbnailsVisible ? "hidden" : "adaptive")}
|
|
480
480
|
className={`h-7 px-2 rounded-md text-[11px] font-medium transition-colors ${
|
|
481
481
|
thumbnailsVisible
|
|
482
|
-
? "bg-studio-accent/10 text-
|
|
483
|
-
: "text-neutral-400 hover:bg-
|
|
482
|
+
? "bg-studio-accent/10 text-accent-ink"
|
|
483
|
+
: "text-neutral-400 hover:bg-hover hover:text-neutral-200"
|
|
484
484
|
}`}
|
|
485
485
|
>
|
|
486
486
|
<Image size={16} aria-hidden="true" />
|
|
@@ -494,8 +494,8 @@ export function TimelineToolbar({
|
|
|
494
494
|
aria-pressed={zoomMode === "fit"}
|
|
495
495
|
className={`h-7 px-2 rounded-md text-[11px] font-medium transition-colors ${
|
|
496
496
|
zoomMode === "fit"
|
|
497
|
-
? "bg-studio-accent/10 text-
|
|
498
|
-
: "text-neutral-400 hover:bg-
|
|
497
|
+
? "bg-studio-accent/10 text-accent-ink"
|
|
498
|
+
: "text-neutral-400 hover:bg-hover hover:text-neutral-200"
|
|
499
499
|
}`}
|
|
500
500
|
>
|
|
501
501
|
<ArrowsOutLineHorizontal size={16} aria-hidden="true" />
|
|
@@ -531,7 +531,7 @@ export function TimelineToolbar({
|
|
|
531
531
|
}}
|
|
532
532
|
// h-6 on the input is the 24x24 WCAG 2.2 (2.5.8) target: the visible
|
|
533
533
|
// track stays 2px and the thumb 10px, only the pointer box grows.
|
|
534
|
-
className="mx-1 h-6 w-[96px] cursor-pointer appearance-none bg-transparent [&::-webkit-slider-runnable-track]:h-[2px] [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-neutral-700 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-[10px] [&::-webkit-slider-thumb]:h-[10px] [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-
|
|
534
|
+
className="mx-1 h-6 w-[96px] cursor-pointer appearance-none bg-transparent [&::-webkit-slider-runnable-track]:h-[2px] [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-neutral-700 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-[10px] [&::-webkit-slider-thumb]:h-[10px] [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-text-0 [&::-webkit-slider-thumb]:-mt-1 [&::-webkit-slider-thumb]:shadow-[0_0_0_2px_#0a0a0a,0_1px_3px_rgba(0,0,0,0.5)] [&::-webkit-slider-thumb]:cursor-grab [&::-webkit-slider-thumb:active]:cursor-grabbing"
|
|
535
535
|
/>
|
|
536
536
|
<Tooltip label="Zoom in">
|
|
537
537
|
<button
|
|
@@ -301,7 +301,7 @@ function WindowMenu() {
|
|
|
301
301
|
onClick={() => togglePanel(id)}
|
|
302
302
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-[11px] text-neutral-300 hover:bg-neutral-800"
|
|
303
303
|
>
|
|
304
|
-
<span className="w-3 text-
|
|
304
|
+
<span className="w-3 text-accent-ink">{openPanels.has(id) ? "✓" : ""}</span>
|
|
305
305
|
{PANEL_DEFINITIONS[id].title}
|
|
306
306
|
</button>
|
|
307
307
|
))}
|