@hyperframes/studio 0.8.103 → 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-DG795Hwu.js → hyperframes-player-tnfCiVdR.js} +1 -1
- package/dist/assets/{index-BiEtDkae.js → index-Ca6LZlVb.js} +1 -1
- package/dist/assets/index-Cre9X4RS.css +1 -0
- package/dist/assets/{index-BPJRf2X9.js → index-DEDhPmPD.js} +215 -215
- package/dist/assets/{index-DuCZ3pQw.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.d.ts +181 -168
- package/dist/index.html +2 -2
- package/dist/index.js +1018 -919
- 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/DomEditOverlay.tsx +5 -6
- 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/PropertyPanel.tsx +3 -3
- 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/anchoredResizeCommitFeedsOffset.test.ts +46 -6
- package/src/components/editor/anchoredResizeReleaseShift.test.ts +0 -84
- package/src/components/editor/domEditOverlayGestures.ts +13 -19
- package/src/components/editor/domEditOverlayStartGesture.test.ts +170 -13
- package/src/components/editor/domEditOverlayStartGesture.ts +15 -25
- package/src/components/editor/easeCurveSvg.tsx +1 -1
- package/src/components/editor/manualEdits.test.ts +3 -9
- package/src/components/editor/manualEdits.ts +0 -1
- package/src/components/editor/manualEditsDom.ts +3 -13
- package/src/components/editor/manualEditsDomPatches.test.ts +3 -131
- package/src/components/editor/manualEditsDomPatches.ts +1 -53
- package/src/components/editor/manualEditsSnapshot.ts +3 -0
- package/src/components/editor/manualEditsTypes.ts +2 -0
- package/src/components/editor/manualOffsetDrag.test.ts +41 -0
- package/src/components/editor/manualOffsetDrag.ts +6 -27
- package/src/components/editor/persistSeam.integration.test.ts +0 -18
- package/src/components/editor/plainTranslate.ts +42 -6
- 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/propertyPanelTransformCommit.test.ts +29 -0
- package/src/components/editor/propertyPanelTransformCommit.ts +8 -5
- package/src/components/editor/propertyPanelTypes.ts +1 -0
- package/src/components/editor/propertyPanelValueTier.test.ts +1 -1
- package/src/components/editor/propertyPanelValueTier.ts +1 -1
- package/src/components/editor/rotateInlineSave.test.ts +111 -0
- package/src/components/editor/rotationDraft.test.ts +111 -0
- package/src/components/editor/rotationDraft.ts +145 -0
- package/src/components/editor/useDomEditNudge.test.tsx +33 -0
- package/src/components/editor/useDomEditNudge.ts +6 -2
- package/src/components/editor/useDomEditOverlayGestures.ts +22 -24
- 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/draggedGsapPosition.ts +28 -0
- package/src/hooks/elementOffsetStager.ts +34 -13
- package/src/hooks/gsapRuntimeBridge.ts +5 -3
- package/src/hooks/gsapRuntimeKeyframes.ts +36 -2
- package/src/hooks/plainRotation.test.ts +122 -0
- package/src/hooks/plainRotation.ts +42 -0
- 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/useDomEditSession.ts +5 -5
- package/src/hooks/useDomGeometryCommit.test.tsx +79 -1
- package/src/hooks/useDomGeometryCommit.ts +16 -17
- package/src/hooks/useDomGeometryCommits.test.tsx +233 -33
- package/src/hooks/useDomGeometryCommits.ts +27 -80
- package/src/hooks/useGsapAwareEditing.test.tsx +194 -11
- package/src/hooks/useGsapAwareEditing.ts +43 -42
- package/src/hooks/useInlineTextEdit.test.tsx +33 -3
- package/src/hooks/useInlineTextEdit.ts +2 -2
- 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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { memo, useState, useCallback, useRef, useEffect } from "react";
|
|
2
|
+
import { toastSurface } from "../StudioToast";
|
|
2
3
|
import {
|
|
3
4
|
trackStudioFeedback,
|
|
4
5
|
trackStudioFeedbackShown,
|
|
@@ -212,24 +213,11 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
212
213
|
}}
|
|
213
214
|
>
|
|
214
215
|
<div
|
|
215
|
-
className=
|
|
216
|
-
style={{
|
|
217
|
-
background: failed
|
|
218
|
-
? "linear-gradient(135deg, rgba(127,29,29,0.55), rgba(80,10,10,0.45))"
|
|
219
|
-
: "linear-gradient(135deg, rgba(38,38,38,0.55), rgba(23,23,23,0.45))",
|
|
220
|
-
backdropFilter: "blur(16px) saturate(1.6)",
|
|
221
|
-
WebkitBackdropFilter: "blur(16px) saturate(1.6)",
|
|
222
|
-
border: `1px solid ${failed ? "rgba(239,68,68,0.18)" : "rgba(255,255,255,0.08)"}`,
|
|
223
|
-
boxShadow: [
|
|
224
|
-
"0 8px 32px rgba(0,0,0,0.35)",
|
|
225
|
-
`inset 0 1px 0 ${failed ? "rgba(239,68,68,0.12)" : "rgba(255,255,255,0.06)"}`,
|
|
226
|
-
"inset 0 -1px 0 rgba(0,0,0,0.15)",
|
|
227
|
-
].join(", "),
|
|
228
|
-
}}
|
|
216
|
+
className={`flex w-[min(340px,calc(100vw-48px))] flex-col gap-2.5 overflow-hidden rounded-2xl px-3.5 py-3 ${toastSurface(failed)}`}
|
|
229
217
|
>
|
|
230
218
|
<div className="flex items-start gap-3">
|
|
231
219
|
<p
|
|
232
|
-
className={`min-w-0 flex-1 text-[12px] leading-5 ${failed ? "text-
|
|
220
|
+
className={`min-w-0 flex-1 text-[12px] leading-5 ${failed ? "text-danger-ink" : "text-text-0"}`}
|
|
233
221
|
>
|
|
234
222
|
{step === "thanks" ? "Thanks, that helps." : title}
|
|
235
223
|
</p>
|
|
@@ -243,7 +231,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
243
231
|
if (req) trackStudioFeedbackInterviewClick({ reason: req.reason });
|
|
244
232
|
close("sent");
|
|
245
233
|
}}
|
|
246
|
-
className="h-6 shrink-0 whitespace-nowrap rounded-full border border-
|
|
234
|
+
className="h-6 shrink-0 whitespace-nowrap rounded-full border border-border-strong bg-hover px-2.5 text-[11px] leading-6 text-neutral-100 transition-[background-color,border-color,transform] duration-150 ease-out hover:border-text-muted hover:bg-press active:scale-[0.97] motion-reduce:transition-none"
|
|
247
235
|
>
|
|
248
236
|
Talk to us, 30 min
|
|
249
237
|
</a>
|
|
@@ -251,7 +239,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
251
239
|
<button
|
|
252
240
|
type="button"
|
|
253
241
|
onClick={() => close("close")}
|
|
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-
|
|
242
|
+
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-press hover:text-neutral-200"
|
|
255
243
|
aria-label="Dismiss"
|
|
256
244
|
>
|
|
257
245
|
<svg
|
|
@@ -270,7 +258,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
270
258
|
{/* The failure text the user already saw, quoted back so the report
|
|
271
259
|
carries it without asking them to retype the error. */}
|
|
272
260
|
{failed && request.detail && step !== "thanks" && (
|
|
273
|
-
<p className="line-clamp-2 text-[11px] leading-4 text-
|
|
261
|
+
<p className="line-clamp-2 text-[11px] leading-4 text-danger-ink">{request.detail}</p>
|
|
274
262
|
)}
|
|
275
263
|
|
|
276
264
|
{step === "rating" && (
|
|
@@ -283,7 +271,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
283
271
|
key={n}
|
|
284
272
|
// A resting fill, so the row reads as eleven controls rather
|
|
285
273
|
// 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-
|
|
274
|
+
className="flex h-7 cursor-pointer items-center justify-center rounded-md bg-hover text-[11px] tabular-nums text-neutral-400 transition-[background-color,color,transform] duration-150 ease-out hover:bg-press hover:text-neutral-100 active:scale-[0.97] has-checked:bg-text-0 has-checked:text-bg-0 has-focus-visible:ring-1 has-focus-visible:ring-ring motion-reduce:transition-none"
|
|
287
275
|
>
|
|
288
276
|
<input
|
|
289
277
|
type="radio"
|
|
@@ -328,8 +316,8 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
328
316
|
// attached to nothing.
|
|
329
317
|
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
318
|
failed
|
|
331
|
-
? "border-
|
|
332
|
-
: "border-
|
|
319
|
+
? "border-danger/30 bg-danger/10 text-danger-ink hover:border-danger/60 hover:bg-danger/20"
|
|
320
|
+
: "border-border bg-hover text-neutral-300 hover:border-text-muted hover:bg-press hover:text-text-0"
|
|
333
321
|
}`}
|
|
334
322
|
>
|
|
335
323
|
{preset.label}
|
|
@@ -355,12 +343,12 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
|
|
|
355
343
|
placeholder={followUp.placeholder}
|
|
356
344
|
aria-label={followUp.prompt}
|
|
357
345
|
maxLength={500}
|
|
358
|
-
className="h-7 min-w-0 flex-1 rounded-md border border-
|
|
346
|
+
className="h-7 min-w-0 flex-1 rounded-md border border-border bg-input px-2 text-[11px] text-neutral-100 outline-hidden transition-colors duration-150 placeholder:text-neutral-500 focus:border-ring"
|
|
359
347
|
/>
|
|
360
348
|
<button
|
|
361
349
|
type="button"
|
|
362
350
|
onClick={() => submit()}
|
|
363
|
-
className="h-7 shrink-0 rounded-md bg-
|
|
351
|
+
className="h-7 shrink-0 rounded-md bg-press px-2.5 text-[11px] text-neutral-100 transition-[background-color,transform] duration-150 ease-out hover:bg-on active:scale-[0.97] motion-reduce:transition-none"
|
|
364
352
|
>
|
|
365
353
|
Send
|
|
366
354
|
</button>
|
|
@@ -142,7 +142,7 @@ export function AssetPreviewOverlay() {
|
|
|
142
142
|
>
|
|
143
143
|
{/* Close button */}
|
|
144
144
|
<button
|
|
145
|
-
className="absolute top-2 right-2 w-6 h-6 rounded-full bg-
|
|
145
|
+
className="absolute top-2 right-2 w-6 h-6 rounded-full bg-raised hover:bg-press text-text-2 hover:text-text-0 flex items-center justify-center transition-colors z-10"
|
|
146
146
|
onClick={(e) => {
|
|
147
147
|
e.stopPropagation();
|
|
148
148
|
clearPreviewAsset();
|
|
@@ -215,7 +215,7 @@ function useMeterLoop(
|
|
|
215
215
|
|
|
216
216
|
function Bar({ maskRef, peakRef }: { maskRef: Ref<HTMLDivElement>; peakRef: Ref<HTMLDivElement> }) {
|
|
217
217
|
return (
|
|
218
|
-
<div className="relative h-full w-[18px] overflow-hidden rounded-[2px] bg-neutral-900">
|
|
218
|
+
<div className="scheme-dark relative h-full w-[18px] overflow-hidden rounded-[2px] bg-neutral-900">
|
|
219
219
|
<div
|
|
220
220
|
className="absolute inset-x-0 bottom-0 bg-green-500"
|
|
221
221
|
style={{ height: `${AMBER_AT * 100}%` }}
|
|
@@ -237,7 +237,7 @@ function Bar({ maskRef, peakRef }: { maskRef: Ref<HTMLDivElement>; peakRef: Ref<
|
|
|
237
237
|
<div
|
|
238
238
|
ref={peakRef}
|
|
239
239
|
data-testid="meter-peak"
|
|
240
|
-
className="absolute inset-x-0 bottom-0 h-px bg-
|
|
240
|
+
className="absolute inset-x-0 bottom-0 h-px bg-text-0"
|
|
241
241
|
/>
|
|
242
242
|
</div>
|
|
243
243
|
);
|
|
@@ -413,13 +413,14 @@ describe("NLEPreview", () => {
|
|
|
413
413
|
describe("a missing poster", () => {
|
|
414
414
|
const renderUrl =
|
|
415
415
|
"/api/projects/timeline-edit-playground/thumbnail/index.html?t=0&output=source";
|
|
416
|
-
const fetchSpy = vi.fn(() => Promise.resolve(new Response()));
|
|
416
|
+
const fetchSpy = vi.fn((_url: string, _init?: RequestInit) => Promise.resolve(new Response()));
|
|
417
417
|
beforeEach(() => {
|
|
418
418
|
fetchSpy.mockClear();
|
|
419
419
|
vi.stubGlobal("fetch", fetchSpy);
|
|
420
420
|
});
|
|
421
421
|
afterEach(() => vi.unstubAllGlobals());
|
|
422
422
|
|
|
423
|
+
const renders = () => fetchSpy.mock.calls.map(([url]) => url);
|
|
423
424
|
const settle = (
|
|
424
425
|
view: ReturnType<typeof renderPreview>,
|
|
425
426
|
steps: Array<"ready" | "missing" | "loaded">,
|
|
@@ -437,7 +438,10 @@ describe("NLEPreview", () => {
|
|
|
437
438
|
it("is rendered for the next open when the live frame is ready first", () => {
|
|
438
439
|
const view = renderPreview();
|
|
439
440
|
settle(view, ["ready", "missing"]);
|
|
440
|
-
expect(
|
|
441
|
+
expect(renders()).toEqual([renderUrl]);
|
|
442
|
+
expect(fetchSpy.mock.calls[0]?.[1]?.signal, "rendered as scheduler work").toBeInstanceOf(
|
|
443
|
+
AbortSignal,
|
|
444
|
+
);
|
|
441
445
|
expect(view.stage.querySelector('[data-testid="preview-poster"]')).toBeNull();
|
|
442
446
|
view.cleanup();
|
|
443
447
|
});
|
|
@@ -447,7 +451,7 @@ describe("NLEPreview", () => {
|
|
|
447
451
|
settle(view, ["missing"]);
|
|
448
452
|
expect(fetchSpy).not.toHaveBeenCalled();
|
|
449
453
|
settle(view, ["ready", "ready"]);
|
|
450
|
-
expect(
|
|
454
|
+
expect(renders()).toEqual([renderUrl]);
|
|
451
455
|
view.cleanup();
|
|
452
456
|
});
|
|
453
457
|
|
|
@@ -458,7 +462,7 @@ describe("NLEPreview", () => {
|
|
|
458
462
|
settle(view, ["ready"]);
|
|
459
463
|
view.render();
|
|
460
464
|
settle(view, ["ready", "missing"]);
|
|
461
|
-
expect(
|
|
465
|
+
expect(renders()).toEqual([renderUrl]);
|
|
462
466
|
view.cleanup();
|
|
463
467
|
});
|
|
464
468
|
});
|
|
@@ -152,7 +152,7 @@ export function PreviewOverlays({
|
|
|
152
152
|
<button
|
|
153
153
|
type="button"
|
|
154
154
|
onClick={enterCaptionMode}
|
|
155
|
-
className="pointer-events-auto 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-
|
|
155
|
+
className="pointer-events-auto 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-accent-ink focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-studio-accent"
|
|
156
156
|
>
|
|
157
157
|
Edit captions
|
|
158
158
|
</button>
|
|
@@ -116,9 +116,9 @@ export function PreviewPane({
|
|
|
116
116
|
<div
|
|
117
117
|
ref={containerRef}
|
|
118
118
|
// A recessed well inset in its dock card; dropped in fullscreen so the
|
|
119
|
-
// preview fills the screen edge-to-edge.
|
|
120
|
-
className={`flex-1 min-h-0 flex flex-col overflow-hidden bg-[var(--studio-preview-bg,var(--color-neutral-950))] ${
|
|
121
|
-
isFullscreen ? "" : "m-3 rounded-md border border-
|
|
119
|
+
// preview fills the screen edge-to-edge. The player stays dark in both themes.
|
|
120
|
+
className={`scheme-dark flex-1 min-h-0 flex flex-col overflow-hidden bg-[var(--studio-preview-bg,var(--color-neutral-950))] ${
|
|
121
|
+
isFullscreen ? "" : "m-3 rounded-md border border-border"
|
|
122
122
|
}`}
|
|
123
123
|
data-studio-fullscreen-target=""
|
|
124
124
|
>
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from "react";
|
|
2
2
|
import { buildProjectApiPath } from "../../utils/projectRouting";
|
|
3
|
+
import {
|
|
4
|
+
createThumbnailKey,
|
|
5
|
+
thumbnailScheduler,
|
|
6
|
+
type ThumbnailRequest,
|
|
7
|
+
} from "../../player/lib/thumbnailScheduler";
|
|
3
8
|
import type { PreviewCompositionSize } from "../../utils/previewCompositionSize";
|
|
4
9
|
|
|
5
10
|
/** Frame 0 as the thumbnail route last rendered it; `cachedOnly` never starts a render. */
|
|
@@ -65,7 +70,7 @@ export function usePreviewPoster(
|
|
|
65
70
|
const renderMissingPoster = useCallback(() => {
|
|
66
71
|
if (missingForRef.current !== visit || liveReadyForRef.current !== visit) return;
|
|
67
72
|
missingForRef.current = null;
|
|
68
|
-
|
|
73
|
+
renderPosterForNextOpen(projectId);
|
|
69
74
|
}, [visit, projectId]);
|
|
70
75
|
const onLiveReadyToShowChange = useCallback(
|
|
71
76
|
(ready: boolean) => {
|
|
@@ -91,6 +96,29 @@ export function usePreviewPoster(
|
|
|
91
96
|
};
|
|
92
97
|
}
|
|
93
98
|
|
|
99
|
+
/** Renders frame 0 under the thumbnail cap; the lease ends once the render settles. */
|
|
100
|
+
export function renderPosterForNextOpen(projectId: string): void {
|
|
101
|
+
const url = previewPosterUrl(projectId, false);
|
|
102
|
+
const request: ThumbnailRequest = {
|
|
103
|
+
key: createThumbnailKey({ kind: "poster-render", url }),
|
|
104
|
+
projectId,
|
|
105
|
+
sessionEpoch: 0,
|
|
106
|
+
kind: "composition",
|
|
107
|
+
priority: "overscan",
|
|
108
|
+
discardWhenReleased: true,
|
|
109
|
+
load: async (signal) => {
|
|
110
|
+
await (await fetch(url, { signal })).arrayBuffer();
|
|
111
|
+
return { value: { kind: "image", url, aspect: 16 / 9 }, weight: 0 };
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
const settled = () => ["ready", "error"].includes(thumbnailScheduler.getSnapshot(request).status);
|
|
115
|
+
let lease: { release(): void } | null = null;
|
|
116
|
+
lease = thumbnailScheduler.acquire(request, () => {
|
|
117
|
+
if (settled()) lease?.release();
|
|
118
|
+
});
|
|
119
|
+
if (settled()) lease.release();
|
|
120
|
+
}
|
|
121
|
+
|
|
94
122
|
function useVisitNumber(key: string): number {
|
|
95
123
|
const [seen, setSeen] = useState({ key, visit: 0 });
|
|
96
124
|
if (seen.key === key) return seen.visit;
|
|
@@ -85,7 +85,7 @@ export function PreviewZoomOverlay({
|
|
|
85
85
|
</span>
|
|
86
86
|
<button
|
|
87
87
|
type="button"
|
|
88
|
-
className="rounded px-1.5 py-0.5 font-medium text-
|
|
88
|
+
className="rounded px-1.5 py-0.5 font-medium text-accent-ink hover:bg-white/10 transition-colors"
|
|
89
89
|
onClick={onFit}
|
|
90
90
|
aria-label="Fit the whole frame in view"
|
|
91
91
|
data-testid="preview-zoom-fit"
|
|
@@ -413,7 +413,7 @@ export function SlideshowPanel({ scenes, onPersist, onPersistNotes }: SlideshowP
|
|
|
413
413
|
|
|
414
414
|
return (
|
|
415
415
|
<div
|
|
416
|
-
className="flex flex-col h-full overflow-y-auto text-
|
|
416
|
+
className="flex flex-col h-full overflow-y-auto text-text-0"
|
|
417
417
|
onKeyDown={(e) => {
|
|
418
418
|
// In-panel undo — scoped so it never fights the app-level file undo.
|
|
419
419
|
if ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "z") {
|
|
@@ -428,14 +428,14 @@ export function SlideshowPanel({ scenes, onPersist, onPersistNotes }: SlideshowP
|
|
|
428
428
|
{persistError && (
|
|
429
429
|
<div
|
|
430
430
|
role="alert"
|
|
431
|
-
className="flex items-center justify-between gap-2 px-3 py-2 bg-
|
|
431
|
+
className="flex items-center justify-between gap-2 px-3 py-2 bg-danger/15 border-b border-red-500/40"
|
|
432
432
|
>
|
|
433
|
-
<span className="text-[11px] text-
|
|
433
|
+
<span className="text-[11px] text-danger-ink">Changes not saved</span>
|
|
434
434
|
<button
|
|
435
435
|
type="button"
|
|
436
436
|
disabled={retrying}
|
|
437
437
|
onClick={handleRetryPersist}
|
|
438
|
-
className="px-2 py-0.5 text-[10px] rounded-sm bg-
|
|
438
|
+
className="px-2 py-0.5 text-[10px] rounded-sm bg-danger text-on-danger enabled:hover:brightness-95 enabled:active:scale-[0.97] disabled:opacity-50 transition-colors"
|
|
439
439
|
>
|
|
440
440
|
{retrying ? "Retrying…" : "Retry"}
|
|
441
441
|
</button>
|
|
@@ -72,9 +72,7 @@ export function SlideList({
|
|
|
72
72
|
tabIndex={0}
|
|
73
73
|
aria-pressed={isSelected}
|
|
74
74
|
className={`flex items-center gap-2 px-3 py-1.5 rounded cursor-pointer text-[11px] transition-colors ${
|
|
75
|
-
isSelected
|
|
76
|
-
? "bg-studio-accent/20 text-white"
|
|
77
|
-
: "hover:bg-neutral-800/60 text-neutral-300"
|
|
75
|
+
isSelected ? "bg-on text-text-0" : "hover:bg-neutral-800/60 text-neutral-300"
|
|
78
76
|
}`}
|
|
79
77
|
onClick={() => onSelect(scene.id)}
|
|
80
78
|
onKeyDown={(e) => {
|
|
@@ -100,7 +98,7 @@ export function SlideList({
|
|
|
100
98
|
aria-label="Move slide up"
|
|
101
99
|
title="Move up"
|
|
102
100
|
disabled={slideIndex <= 0}
|
|
103
|
-
className="px-1 py-0.5 text-[10px] text-neutral-400 enabled:hover:text-
|
|
101
|
+
className="px-1 py-0.5 text-[10px] text-neutral-400 enabled:hover:text-text-0 enabled:active:scale-[0.95] disabled:opacity-30 disabled:cursor-not-allowed"
|
|
104
102
|
onClick={(e) => {
|
|
105
103
|
e.stopPropagation();
|
|
106
104
|
onReorder(scene.id, "up");
|
|
@@ -113,7 +111,7 @@ export function SlideList({
|
|
|
113
111
|
aria-label="Move slide down"
|
|
114
112
|
title="Move down"
|
|
115
113
|
disabled={slideIndex === slides.length - 1}
|
|
116
|
-
className="px-1 py-0.5 text-[10px] text-neutral-400 enabled:hover:text-
|
|
114
|
+
className="px-1 py-0.5 text-[10px] text-neutral-400 enabled:hover:text-text-0 enabled:active:scale-[0.95] disabled:opacity-30 disabled:cursor-not-allowed"
|
|
117
115
|
onClick={(e) => {
|
|
118
116
|
e.stopPropagation();
|
|
119
117
|
onReorder(scene.id, "down");
|
|
@@ -162,7 +160,7 @@ export function SlideInspector({
|
|
|
162
160
|
<div className="flex flex-col gap-1">
|
|
163
161
|
<label className="text-[11px] text-neutral-400">Notes</label>
|
|
164
162
|
<textarea
|
|
165
|
-
className="bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1.5 text-[11px] text-
|
|
163
|
+
className="bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1.5 text-[11px] text-text-0 resize-none placeholder-neutral-600 focus:border-studio-accent/60 focus:outline-hidden"
|
|
166
164
|
rows={3}
|
|
167
165
|
placeholder="Speaker notes or script..."
|
|
168
166
|
value={slide?.notes ?? ""}
|
|
@@ -174,7 +172,7 @@ export function SlideInspector({
|
|
|
174
172
|
<span className="text-[11px] text-neutral-400">Fragment hold-points</span>
|
|
175
173
|
<button
|
|
176
174
|
type="button"
|
|
177
|
-
className="text-[10px] px-2 py-0.5 rounded-sm bg-
|
|
175
|
+
className="text-[10px] px-2 py-0.5 rounded-sm bg-hover hover:bg-press text-neutral-200 transition-colors"
|
|
178
176
|
onClick={onMarkFragment}
|
|
179
177
|
title={`Mark ${currentTime.toFixed(2)}s as hold-point`}
|
|
180
178
|
>
|
|
@@ -192,7 +190,7 @@ export function SlideInspector({
|
|
|
192
190
|
<button
|
|
193
191
|
type="button"
|
|
194
192
|
aria-label={`Remove fragment at ${t.toFixed(2)}s`}
|
|
195
|
-
className="text-neutral-400 hover:text-
|
|
193
|
+
className="text-neutral-400 hover:text-danger-ink transition-colors"
|
|
196
194
|
onClick={() => onRemoveFragment(t)}
|
|
197
195
|
>
|
|
198
196
|
×
|
|
@@ -249,7 +247,7 @@ export function BranchTree({
|
|
|
249
247
|
<input
|
|
250
248
|
id={inputId}
|
|
251
249
|
type="text"
|
|
252
|
-
className="flex-1 bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1 text-[11px] text-
|
|
250
|
+
className="flex-1 bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1 text-[11px] text-text-0 placeholder-neutral-600 focus:border-studio-accent/60 focus:outline-hidden"
|
|
253
251
|
placeholder="New branch name..."
|
|
254
252
|
value={newLabel}
|
|
255
253
|
onChange={(e) => setNewLabel(e.target.value)}
|
|
@@ -260,7 +258,7 @@ export function BranchTree({
|
|
|
260
258
|
/>
|
|
261
259
|
<button
|
|
262
260
|
type="button"
|
|
263
|
-
className="px-2 py-1 rounded-sm bg-
|
|
261
|
+
className="px-2 py-1 rounded-sm bg-hover hover:bg-press text-[11px] text-neutral-200 transition-colors shrink-0"
|
|
264
262
|
onClick={handleCreate}
|
|
265
263
|
>
|
|
266
264
|
Add
|
|
@@ -326,7 +324,7 @@ function BranchItem({
|
|
|
326
324
|
<div className="flex items-center gap-1">
|
|
327
325
|
{editing ? (
|
|
328
326
|
<input
|
|
329
|
-
className="flex-1 bg-neutral-800 border border-neutral-600 rounded-sm px-1.5 py-0.5 text-[11px] text-
|
|
327
|
+
className="flex-1 bg-neutral-800 border border-neutral-600 rounded-sm px-1.5 py-0.5 text-[11px] text-text-0 focus:border-studio-accent/60 focus:outline-hidden"
|
|
330
328
|
value={draft}
|
|
331
329
|
autoFocus
|
|
332
330
|
onChange={(e) => setDraft(e.target.value)}
|
|
@@ -341,7 +339,7 @@ function BranchItem({
|
|
|
341
339
|
<span
|
|
342
340
|
role="button"
|
|
343
341
|
tabIndex={0}
|
|
344
|
-
className="flex-1 text-[11px] text-
|
|
342
|
+
className="flex-1 text-[11px] text-text-0 font-medium truncate cursor-pointer hover:text-neutral-300"
|
|
345
343
|
title="Click to rename"
|
|
346
344
|
onClick={() => setEditing(true)}
|
|
347
345
|
onKeyDown={(e) => {
|
|
@@ -357,15 +355,15 @@ function BranchItem({
|
|
|
357
355
|
<button
|
|
358
356
|
type="button"
|
|
359
357
|
aria-label={`Delete branch ${seq.label}`}
|
|
360
|
-
className="text-[10px] text-neutral-500 hover:text-
|
|
358
|
+
className="text-[10px] text-neutral-500 hover:text-danger-ink active:scale-[0.95] transition-colors px-1"
|
|
361
359
|
onClick={() => setConfirmingDelete(true)}
|
|
362
360
|
>
|
|
363
361
|
✕
|
|
364
362
|
</button>
|
|
365
363
|
</div>
|
|
366
364
|
{confirmingDelete && (
|
|
367
|
-
<div className="px-2 py-1.5 bg-
|
|
368
|
-
<span className="text-[10px] text-
|
|
365
|
+
<div className="px-2 py-1.5 bg-danger/15 border-l-2 border-red-500 flex flex-col gap-1 rounded-xs">
|
|
366
|
+
<span className="text-[10px] text-danger-ink">
|
|
369
367
|
Delete branch “{seq.label}”
|
|
370
368
|
{seq.slides.length > 0
|
|
371
369
|
? ` and its ${seq.slides.length} slide${seq.slides.length === 1 ? "" : "s"}`
|
|
@@ -379,7 +377,7 @@ function BranchItem({
|
|
|
379
377
|
setConfirmingDelete(false);
|
|
380
378
|
onDelete(seq.id);
|
|
381
379
|
}}
|
|
382
|
-
className="px-2 py-0.5 text-[10px] rounded-sm bg-
|
|
380
|
+
className="px-2 py-0.5 text-[10px] rounded-sm bg-danger text-on-danger hover:brightness-95 active:brightness-90 transition-colors"
|
|
383
381
|
>
|
|
384
382
|
Delete
|
|
385
383
|
</button>
|
|
@@ -414,7 +412,7 @@ function BranchItem({
|
|
|
414
412
|
type="button"
|
|
415
413
|
aria-pressed={isSelected}
|
|
416
414
|
className={`flex-1 text-left truncate transition-colors hover:text-neutral-200 ${
|
|
417
|
-
isSelected ? "text-
|
|
415
|
+
isSelected ? "text-text-0" : "text-neutral-400"
|
|
418
416
|
}`}
|
|
419
417
|
onClick={() => onSelectBranchSlide(seq.id, scene.id)}
|
|
420
418
|
>
|
|
@@ -497,7 +495,7 @@ export function HotspotTool({
|
|
|
497
495
|
<label className="text-[11px] text-neutral-400">Hotspot label</label>
|
|
498
496
|
<input
|
|
499
497
|
type="text"
|
|
500
|
-
className="bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1 text-[11px] text-
|
|
498
|
+
className="bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1 text-[11px] text-text-0 placeholder-neutral-600 focus:border-studio-accent/60 focus:outline-hidden"
|
|
501
499
|
placeholder="Button label..."
|
|
502
500
|
value={hotspotLabel}
|
|
503
501
|
onChange={(e) => setHotspotLabel(e.target.value)}
|
|
@@ -505,7 +503,7 @@ export function HotspotTool({
|
|
|
505
503
|
/>
|
|
506
504
|
<label className="text-[11px] text-neutral-400">Target branch</label>
|
|
507
505
|
<select
|
|
508
|
-
className="bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1 text-[11px] text-
|
|
506
|
+
className="bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1 text-[11px] text-text-0 focus:border-studio-accent/60 focus:outline-hidden"
|
|
509
507
|
value={targetSequenceId}
|
|
510
508
|
onChange={(e) => setTargetSequenceId(e.target.value)}
|
|
511
509
|
aria-label="Target branch sequence"
|
|
@@ -527,7 +525,7 @@ export function HotspotTool({
|
|
|
527
525
|
? "Choose a target branch first"
|
|
528
526
|
: undefined
|
|
529
527
|
}
|
|
530
|
-
className="px-3 py-1.5 rounded-sm bg-
|
|
528
|
+
className="px-3 py-1.5 rounded-sm bg-accent enabled:hover:bg-accent-hover enabled:active:scale-[0.98] text-on-accent text-[11px] font-medium transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
|
531
529
|
onClick={handleMakeHotspot}
|
|
532
530
|
>
|
|
533
531
|
Make hotspot
|
|
@@ -550,7 +548,7 @@ export function HotspotTool({
|
|
|
550
548
|
<button
|
|
551
549
|
type="button"
|
|
552
550
|
aria-label={`Remove hotspot ${h.label}`}
|
|
553
|
-
className="text-[10px] text-neutral-500 hover:text-
|
|
551
|
+
className="text-[10px] text-neutral-500 hover:text-danger-ink transition-colors"
|
|
554
552
|
onClick={() => onRemoveHotspot(selectedSceneId, h.id)}
|
|
555
553
|
>
|
|
556
554
|
✕
|
|
@@ -215,7 +215,7 @@ export function VariablesBindElement({
|
|
|
215
215
|
className={`${VARIABLES_INPUT_CLASS} font-mono`}
|
|
216
216
|
/>
|
|
217
217
|
{existingDecl && (
|
|
218
|
-
<p className="text-[9px] leading-snug text-
|
|
218
|
+
<p className="text-[9px] leading-snug text-warning-ink">
|
|
219
219
|
"{trimmedId}" already exists. This element will use its current value
|
|
220
220
|
{existingDecl.default !== undefined && (
|
|
221
221
|
<span className="font-mono"> ({String(existingDecl.default)})</span>
|
|
@@ -299,7 +299,7 @@ export function DeclarationForm({
|
|
|
299
299
|
className={VARIABLES_INPUT_CLASS}
|
|
300
300
|
/>
|
|
301
301
|
</Field>
|
|
302
|
-
{error && <p className="text-[10px] text-
|
|
302
|
+
{error && <p className="text-[10px] text-danger-ink">{error}</p>}
|
|
303
303
|
<div className="flex items-center justify-end gap-2 pt-1">
|
|
304
304
|
<button
|
|
305
305
|
type="button"
|
|
@@ -56,9 +56,9 @@ function formatIssue(issue: VariableValidationIssue): string {
|
|
|
56
56
|
function ValidationStrip({ issues }: { issues: VariableValidationIssue[] }) {
|
|
57
57
|
if (issues.length === 0) return null;
|
|
58
58
|
return (
|
|
59
|
-
<div className="space-y-1 rounded-lg border border-
|
|
59
|
+
<div className="space-y-1 rounded-lg border border-danger/40 bg-danger/15 p-2">
|
|
60
60
|
{issues.map((issue) => (
|
|
61
|
-
<p key={`${issue.kind}:${issue.variableId}`} className="text-[10px] text-
|
|
61
|
+
<p key={`${issue.kind}:${issue.variableId}`} className="text-[10px] text-danger-ink">
|
|
62
62
|
{formatIssue(issue)}
|
|
63
63
|
</p>
|
|
64
64
|
))}
|
|
@@ -99,7 +99,7 @@ function VariableRow({
|
|
|
99
99
|
</span>
|
|
100
100
|
{unused && (
|
|
101
101
|
<span
|
|
102
|
-
className="rounded-sm bg-amber-
|
|
102
|
+
className="rounded-sm bg-amber-500/15 px-1 py-px text-[8px] text-warning-ink"
|
|
103
103
|
title="No script reads this variable"
|
|
104
104
|
>
|
|
105
105
|
unused
|
|
@@ -175,9 +175,7 @@ function PreviewModeHeader({
|
|
|
175
175
|
<span className="text-[11px] font-semibold text-neutral-200">Variables</span>
|
|
176
176
|
<span
|
|
177
177
|
className={`rounded-full px-2 py-0.5 text-[9px] font-medium ${
|
|
178
|
-
hasOverrides
|
|
179
|
-
? "bg-studio-accent/20 text-studio-accent"
|
|
180
|
-
: "bg-neutral-800 text-neutral-500"
|
|
178
|
+
hasOverrides ? "bg-studio-accent/20 text-accent-ink" : "bg-neutral-800 text-neutral-500"
|
|
181
179
|
}`}
|
|
182
180
|
>
|
|
183
181
|
{hasOverrides ? `Previewing ${overrideCount} custom` : "Previewing defaults"}
|
|
@@ -16,7 +16,7 @@ export function RowAction({
|
|
|
16
16
|
onClick={onClick}
|
|
17
17
|
title={title}
|
|
18
18
|
className={`h-5 rounded px-1.5 text-[9px] text-neutral-500 hover:bg-neutral-800 ${
|
|
19
|
-
danger ? "hover:text-
|
|
19
|
+
danger ? "hover:text-danger-ink" : "hover:text-neutral-200"
|
|
20
20
|
}`}
|
|
21
21
|
>
|
|
22
22
|
{label}
|
|
@@ -67,7 +67,7 @@ export const FfmpegRequiredNotice = memo(function FfmpegRequiredNotice({
|
|
|
67
67
|
className="flex flex-col gap-2 rounded-xl border border-amber-500/30 bg-amber-500/10 p-2.5"
|
|
68
68
|
>
|
|
69
69
|
<div className="flex flex-col gap-0.5">
|
|
70
|
-
<span className="text-[11px] font-semibold text-
|
|
70
|
+
<span className="text-[11px] font-semibold text-warning-ink">
|
|
71
71
|
{status.title ?? "FFmpeg not found"}
|
|
72
72
|
</span>
|
|
73
73
|
{/* text-2, not the panel's usual text-4 for secondary copy: the amber
|
|
@@ -81,7 +81,7 @@ export const FfmpegRequiredNotice = memo(function FfmpegRequiredNotice({
|
|
|
81
81
|
|
|
82
82
|
{status.command ? (
|
|
83
83
|
<div className="flex items-center gap-1.5">
|
|
84
|
-
<code className="flex-1 overflow-x-auto whitespace-nowrap rounded-xs bg-
|
|
84
|
+
<code className="flex-1 overflow-x-auto whitespace-nowrap rounded-xs bg-input px-2 py-1 text-[10px] text-panel-text-2">
|
|
85
85
|
{status.command}
|
|
86
86
|
</code>
|
|
87
87
|
{/* Fixed width so swapping the label to "Copied" cannot shift the
|
|
@@ -89,7 +89,7 @@ export const FfmpegRequiredNotice = memo(function FfmpegRequiredNotice({
|
|
|
89
89
|
<button
|
|
90
90
|
type="button"
|
|
91
91
|
onClick={() => void copy(status.command ?? "")}
|
|
92
|
-
className={`h-6 w-14 shrink-0 rounded-xs border border-amber-500/30 text-[10px] font-medium text-
|
|
92
|
+
className={`h-6 w-14 shrink-0 rounded-xs border border-amber-500/30 text-[10px] font-medium text-warning-ink transition-colors hover:bg-amber-500/20 active:scale-[0.98] ${FOCUS_RING}`}
|
|
93
93
|
>
|
|
94
94
|
{copied ? "Copied" : "Copy"}
|
|
95
95
|
</button>
|
|
@@ -107,7 +107,7 @@ export const FfmpegRequiredNotice = memo(function FfmpegRequiredNotice({
|
|
|
107
107
|
type="button"
|
|
108
108
|
onClick={onRecheck}
|
|
109
109
|
disabled={checking}
|
|
110
|
-
className={`rounded-xs py-0.5 text-[10px] font-medium text-
|
|
110
|
+
className={`rounded-xs py-0.5 text-[10px] font-medium text-warning-ink underline-offset-2 transition-colors hover:underline disabled:opacity-50 ${FOCUS_RING}`}
|
|
111
111
|
>
|
|
112
112
|
{checking ? "Checking…" : "Recheck"}
|
|
113
113
|
</button>
|
|
@@ -467,7 +467,7 @@ export const RenderQueue = memo(function RenderQueue({
|
|
|
467
467
|
role="alert"
|
|
468
468
|
className="flex items-start justify-between gap-2 px-3 py-2 border-b border-border bg-danger/10"
|
|
469
469
|
>
|
|
470
|
-
<span className="text-step-10 text-danger">{actionError}</span>
|
|
470
|
+
<span className="text-step-10 text-danger-ink">{actionError}</span>
|
|
471
471
|
{onDismissActionError && (
|
|
472
472
|
<IconButton
|
|
473
473
|
size="sm"
|
|
@@ -497,7 +497,7 @@ export const RenderQueue = memo(function RenderQueue({
|
|
|
497
497
|
<div ref={listRef} className="flex-1 overflow-y-auto">
|
|
498
498
|
{loadError && jobs.length === 0 ? (
|
|
499
499
|
<div className="flex flex-col items-center justify-center h-full px-4 gap-2" role="alert">
|
|
500
|
-
<p className="text-step-10 text-danger text-center">{loadError}</p>
|
|
500
|
+
<p className="text-step-10 text-danger-ink text-center">{loadError}</p>
|
|
501
501
|
{onRetryLoad && (
|
|
502
502
|
<Button size="sm" variant="secondary" onClick={onRetryLoad}>
|
|
503
503
|
Retry
|
|
@@ -115,7 +115,7 @@ export const RenderQueueItem = memo(function RenderQueueItem({
|
|
|
115
115
|
)}
|
|
116
116
|
{job.status === "failed" && (
|
|
117
117
|
<div className="w-full h-full flex items-center justify-center">
|
|
118
|
-
<div className="w-2 h-2 rounded-full bg-danger" />
|
|
118
|
+
<div className="w-2 h-2 rounded-full bg-danger-ink" />
|
|
119
119
|
</div>
|
|
120
120
|
)}
|
|
121
121
|
{job.status === "cancelled" && (
|
|
@@ -140,7 +140,7 @@ export const RenderQueueItem = memo(function RenderQueueItem({
|
|
|
140
140
|
<div className="mt-1">
|
|
141
141
|
<div className="flex items-center justify-between mb-0.5">
|
|
142
142
|
<span className="text-step-9 text-text-4">{job.stage || "Rendering"}</span>
|
|
143
|
-
<span className="text-step-9 font-mono text-accent">{job.progress}%</span>
|
|
143
|
+
<span className="text-step-9 font-mono text-accent-ink">{job.progress}%</span>
|
|
144
144
|
</div>
|
|
145
145
|
<div
|
|
146
146
|
className="w-full h-1 bg-border rounded-full overflow-hidden"
|
|
@@ -159,7 +159,7 @@ export const RenderQueueItem = memo(function RenderQueueItem({
|
|
|
159
159
|
)}
|
|
160
160
|
|
|
161
161
|
{job.status === "failed" && job.error && (
|
|
162
|
-
<span className="text-step-9 text-danger mt-0.5 block">{job.error}</span>
|
|
162
|
+
<span className="text-step-9 text-danger-ink mt-0.5 block">{job.error}</span>
|
|
163
163
|
)}
|
|
164
164
|
{job.status === "cancelled" && (
|
|
165
165
|
<span className="text-step-9 text-text-4 mt-0.5 block">Cancelled</span>
|
|
@@ -31,7 +31,7 @@ function CopyChip({ feedback, asset }: { feedback: CopyFeedback; asset: string }
|
|
|
31
31
|
<span
|
|
32
32
|
role="status"
|
|
33
33
|
className={`shrink-0 text-[9px] font-medium px-1.5 py-px rounded ${
|
|
34
|
-
feedback.ok ? "text-
|
|
34
|
+
feedback.ok ? "text-accent-ink bg-panel-accent/10" : "text-danger-ink bg-red-500/10"
|
|
35
35
|
}`}
|
|
36
36
|
>
|
|
37
37
|
{feedback.ok ? "Copied" : "Copy failed"}
|
|
@@ -355,7 +355,7 @@ export function FontRow({
|
|
|
355
355
|
}`}
|
|
356
356
|
>
|
|
357
357
|
<div className="w-[50px] h-[32px] rounded-sm overflow-hidden bg-neutral-900 shrink-0 flex items-center justify-center">
|
|
358
|
-
<span className="text-[9px] font-medium text-
|
|
358
|
+
<span className="text-[9px] font-medium text-text-muted">{extension}</span>
|
|
359
359
|
</div>
|
|
360
360
|
<div className="min-w-0 flex-1">
|
|
361
361
|
<span
|
|
@@ -366,7 +366,7 @@ export function FontRow({
|
|
|
366
366
|
<div className="flex items-center gap-1.5">
|
|
367
367
|
<span className="text-[10px] text-neutral-600 truncate">{extension}</span>
|
|
368
368
|
{used && (
|
|
369
|
-
<span className="text-[9px] font-medium text-
|
|
369
|
+
<span className="text-[9px] font-medium text-accent-ink bg-panel-accent/10 px-1.5 py-px rounded-sm">
|
|
370
370
|
in use
|
|
371
371
|
</span>
|
|
372
372
|
)}
|