@hyperframes/studio 0.8.190 → 0.8.192

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/assets/{hyperframes-player-0HXJPmYC.js → hyperframes-player-ChN3cOr0.js} +3 -3
  2. package/dist/assets/{index-DsSfhjUS.js → index-BYbFcRR0.js} +1 -1
  3. package/dist/assets/{index-BYw97F07.js → index-CHu83hHM.js} +126 -125
  4. package/dist/assets/{index-B3vk3hfU.css → index-CYbvOVp-.css} +1 -1
  5. package/dist/{chunk-563JPIL4.js → chunk-6SXUUBPD.js} +9 -3
  6. package/dist/{chunk-FF5DM7BQ.js → chunk-7HZGKFFU.js} +18 -4
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.html +2 -2
  9. package/dist/index.js +509 -282
  10. package/dist/player.js +2 -2
  11. package/dist/styles.css +1 -1
  12. package/dist/ui.d.ts +3 -1
  13. package/dist/ui.js +1 -1
  14. package/package.json +9 -8
  15. package/src/captions/parser.dom.test.ts +126 -0
  16. package/src/captions/parser.test.ts +71 -1
  17. package/src/captions/parser.ts +121 -65
  18. package/src/components/StudioRightPanels.tsx +9 -0
  19. package/src/components/dock/DockTab.tsx +2 -0
  20. package/src/components/dock/dockAccessibility.test.ts +3 -3
  21. package/src/components/dock/dockLayout.ts +1 -1
  22. package/src/components/dock/panelRegistry.ts +6 -0
  23. package/src/components/panels/MotionBlurPanel.test.tsx +177 -0
  24. package/src/components/panels/MotionBlurPanel.tsx +163 -0
  25. package/src/components/panels/motionBlurPanelModel.test.ts +143 -0
  26. package/src/components/panels/motionBlurPanelModel.ts +98 -0
  27. package/src/components/ui/Tooltip.test.tsx +32 -0
  28. package/src/components/ui/Tooltip.tsx +23 -3
  29. package/src/hooks/usePanelLayout.test.ts +1 -1
  30. package/src/player/components/TimelineClip.filmstrip.test.tsx +1 -1
  31. package/src/player/components/timelineMotionStyles.test.ts +1 -1
  32. package/src/styles/theme.css +1 -1
  33. package/src/utils/studioHelpers.ts +2 -1
  34. package/src/utils/studioUrlState.ts +8 -1
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  usePreviewVariablesStore,
49
49
  whenPreviewBooted,
50
50
  writeStudioUiPreferences
51
- } from "./chunk-563JPIL4.js";
51
+ } from "./chunk-6SXUUBPD.js";
52
52
  import {
53
53
  Button,
54
54
  ContextMenu,
@@ -80,7 +80,7 @@ import {
80
80
  fieldBase,
81
81
  fieldText,
82
82
  popupSurface
83
- } from "./chunk-FF5DM7BQ.js";
83
+ } from "./chunk-7HZGKFFU.js";
84
84
  import {
85
85
  DOM_EDIT_LAYER_OBSERVER_INIT,
86
86
  buildDefaultDomEditTextField,
@@ -36337,11 +36337,11 @@ function ownNodeTransform(node, ops, getStyle) {
36337
36337
  const spin = individualRotateDegrees(style.rotate);
36338
36338
  return spin === 0 ? own : ops.compose(ops.fromRotate(spin), own);
36339
36339
  }
36340
- function composeElementTransform(element, ops, getStyle, memo63) {
36340
+ function composeElementTransform(element, ops, getStyle, memo64) {
36341
36341
  const pending2 = [];
36342
36342
  let above;
36343
36343
  for (let node = element; node; node = node.parentElement) {
36344
- above = memo63?.get(node);
36344
+ above = memo64?.get(node);
36345
36345
  if (above !== void 0) break;
36346
36346
  pending2.push(node);
36347
36347
  if (node.hasAttribute(COMPOSITION_ROOT_ATTR)) break;
@@ -36353,7 +36353,7 @@ function composeElementTransform(element, ops, getStyle, memo63) {
36353
36353
  const own = ownNodeTransform(node, ops, getStyle);
36354
36354
  acc = own === null ? null : ops.compose(acc, own);
36355
36355
  }
36356
- memo63?.set(node, acc);
36356
+ memo64?.set(node, acc);
36357
36357
  }
36358
36358
  return acc;
36359
36359
  }
@@ -36636,11 +36636,11 @@ function createOverlayMeasurePass() {
36636
36636
  sourceBoundaryRect: /* @__PURE__ */ new Map()
36637
36637
  };
36638
36638
  }
36639
- function readThroughPass(memo63, key2, compute) {
36640
- const answered = memo63.get(key2);
36639
+ function readThroughPass(memo64, key2, compute) {
36640
+ const answered = memo64.get(key2);
36641
36641
  if (answered !== void 0) return answered;
36642
36642
  const value = compute();
36643
- memo63.set(key2, value);
36643
+ memo64.set(key2, value);
36644
36644
  return value;
36645
36645
  }
36646
36646
 
@@ -51878,6 +51878,7 @@ function DockStripActions({
51878
51878
  // src/components/dock/DockTab.tsx
51879
51879
  import { useCallback as useCallback72, useSyncExternalStore as useSyncExternalStore9 } from "react";
51880
51880
  import {
51881
+ Aperture,
51881
51882
  BracketsCurly,
51882
51883
  ChartBarHorizontal,
51883
51884
  Code,
@@ -51903,7 +51904,8 @@ var TAB_ICONS = {
51903
51904
  layers: Stack2,
51904
51905
  renders: FilmSlate,
51905
51906
  variables: BracketsCurly,
51906
- slideshow: Presentation
51907
+ slideshow: Presentation,
51908
+ motionBlur: Aperture
51907
51909
  };
51908
51910
  function DockTab({ api }) {
51909
51911
  const subscribe2 = useCallback72(
@@ -52041,7 +52043,7 @@ var MIN_SIDE_W = 200;
52041
52043
  var MIN_SIDE_W_FLOOR = 120;
52042
52044
  var DEFAULT_TIMELINE_H = 360;
52043
52045
  var DEFAULT_LEFT = ["compositions", "assets", "code", "catalog"];
52044
- var DEFAULT_RIGHT = ["design", "layers", "renders", "variables"];
52046
+ var DEFAULT_RIGHT = ["design", "layers", "renders", "variables", "motionBlur"];
52045
52047
  function defaultSideWidths(viewportWidth) {
52046
52048
  const floor = sideMinimumWidth(viewportWidth);
52047
52049
  const left = Math.max(floor, Math.min(384, Math.round(viewportWidth * 0.257)));
@@ -72917,7 +72919,7 @@ var FileTree = memo43(function FileTree2({
72917
72919
  });
72918
72920
 
72919
72921
  // src/App.tsx
72920
- import { useState as useState164, useCallback as useCallback194, useRef as useRef189, useMemo as useMemo89 } from "react";
72922
+ import { useState as useState165, useCallback as useCallback195, useRef as useRef190, useMemo as useMemo90 } from "react";
72921
72923
 
72922
72924
  // src/hooks/useStableHandlers.ts
72923
72925
  import { useRef as useRef117 } from "react";
@@ -88829,6 +88831,12 @@ function useClipboard({
88829
88831
  // src/hooks/useCaptionDetection.ts
88830
88832
  import { useEffect as useEffect129, useRef as useRef161 } from "react";
88831
88833
 
88834
+ // src/captions/parser.ts
88835
+ import {
88836
+ parseExpressionAt,
88837
+ tokenizer
88838
+ } from "acorn";
88839
+
88832
88840
  // src/captions/types.ts
88833
88841
  var DEFAULT_STYLE = {
88834
88842
  fontFamily: "sans-serif",
@@ -88887,19 +88895,26 @@ var DEFAULT_ANIMATION_SET = {
88887
88895
 
88888
88896
  // src/captions/parser.ts
88889
88897
  function buildCaptionModel(transcript, options) {
88890
- const { width, height, duration, wordsPerGroup = 5 } = options;
88898
+ const { wordsPerGroup = 5 } = options;
88899
+ const wordGroups = [];
88900
+ for (let offset = 0; offset < transcript.length; offset += wordsPerGroup) {
88901
+ wordGroups.push(transcript.slice(offset, offset + wordsPerGroup));
88902
+ }
88903
+ return buildGroupedCaptionModel(wordGroups, options);
88904
+ }
88905
+ function buildGroupedCaptionModel(wordGroups, { width, height, duration }) {
88891
88906
  const segments = /* @__PURE__ */ new Map();
88892
88907
  const groups = /* @__PURE__ */ new Map();
88893
88908
  const groupOrder = [];
88894
- for (let groupIdx = 0; groupIdx < transcript.length; groupIdx += wordsPerGroup) {
88895
- const chunk2 = transcript.slice(groupIdx, groupIdx + wordsPerGroup);
88896
- const groupId = `group-${groupIdx / wordsPerGroup}`;
88909
+ for (const chunk2 of wordGroups) {
88910
+ const groupId = `group-${groups.size}`;
88897
88911
  const segmentIds = [];
88898
88912
  chunk2.forEach((word, wordIdx) => {
88899
- const segmentId = `segment-${groupIdx + wordIdx}`;
88913
+ const segmentIndex = segments.size;
88914
+ const segmentId = `segment-${segmentIndex}`;
88900
88915
  const segment = {
88901
88916
  id: segmentId,
88902
- wordId: word.id ?? `w${groupIdx + wordIdx}`,
88917
+ wordId: word.id ?? `w${segmentIndex}`,
88903
88918
  text: word.text,
88904
88919
  start: word.start,
88905
88920
  end: word.end,
@@ -88938,15 +88953,33 @@ function buildCaptionModel(transcript, options) {
88938
88953
  }
88939
88954
  };
88940
88955
  }
88956
+ var GROUP_SELECTOR = ".caption-group, .caption-line, .caption-block";
88957
+ var WORD_SELECTOR = ".word, .caption-word";
88958
+ function readTranscriptGroups(transcript, groupEls) {
88959
+ const wordGroups = [];
88960
+ let offset = 0;
88961
+ for (const group of groupEls) {
88962
+ if (group.parentElement?.closest(GROUP_SELECTOR)) continue;
88963
+ const size = group.querySelectorAll(WORD_SELECTOR).length;
88964
+ if (size === 0) continue;
88965
+ wordGroups.push(transcript.slice(offset, offset + size));
88966
+ offset += size;
88967
+ }
88968
+ return offset === transcript.length ? wordGroups : null;
88969
+ }
88941
88970
  function extractTranscript(source) {
88942
- const varPattern = /(?:const|let|var)\s+(?:TRANSCRIPT|script)\s*=\s*(\[[\s\S]*?\]);/;
88943
- const match = source.match(varPattern);
88971
+ const varPattern = /\b(?:const|let|var)\s+(?:TRANSCRIPT|script)\s*=\s*(?=\[)/;
88972
+ const match = varPattern.exec(source);
88944
88973
  if (!match) {
88945
88974
  return [];
88946
88975
  }
88947
- const arrayLiteral = match[1];
88948
88976
  try {
88949
- return parseTranscriptArray(arrayLiteral);
88977
+ const options = { ecmaVersion: "latest" };
88978
+ const expression = parseExpressionAt(source, match.index + match[0].length, options);
88979
+ if (expression.type !== "ArrayExpression") return [];
88980
+ const nextToken = tokenizer(source.slice(expression.end), options).getToken();
88981
+ if (nextToken.type.label !== ";") return [];
88982
+ return parseTranscriptArray(readStaticValue(expression));
88950
88983
  } catch {
88951
88984
  return [];
88952
88985
  }
@@ -88956,21 +88989,15 @@ function parseCaptionComposition(iframeDoc, iframeWin, source, compositionWidth,
88956
88989
  if (transcript.length === 0) {
88957
88990
  return null;
88958
88991
  }
88959
- const groupEls = iframeDoc.querySelectorAll(".caption-group, .caption-line, .caption-block");
88960
- const wordEls = iframeDoc.querySelectorAll(".word, .caption-word");
88961
- let wordsPerGroup = 5;
88962
- if (groupEls.length > 0 && wordEls.length > 0) {
88963
- wordsPerGroup = Math.round(wordEls.length / groupEls.length);
88964
- if (wordsPerGroup < 1) {
88965
- wordsPerGroup = 1;
88966
- }
88967
- }
88968
- const model = buildCaptionModel(transcript, {
88992
+ const groupEls = iframeDoc.querySelectorAll(GROUP_SELECTOR);
88993
+ const wordEls = iframeDoc.querySelectorAll(WORD_SELECTOR);
88994
+ const wordGroups = readTranscriptGroups(transcript, groupEls);
88995
+ const options = {
88969
88996
  width: compositionWidth,
88970
88997
  height: compositionHeight,
88971
- duration: compositionDuration,
88972
- wordsPerGroup
88973
- });
88998
+ duration: compositionDuration
88999
+ };
89000
+ const model = wordGroups ? buildGroupedCaptionModel(wordGroups, options) : buildCaptionModel(transcript, options);
88974
89001
  const firstWordEl = wordEls.item(0);
88975
89002
  const firstGroupEl = groupEls.item(0);
88976
89003
  const styleSourceEl = firstWordEl ?? firstGroupEl;
@@ -89036,32 +89063,49 @@ function parseCaptionComposition(iframeDoc, iframeWin, source, compositionWidth,
89036
89063
  }
89037
89064
  return model;
89038
89065
  }
89039
- function parseTranscriptArray(arrayLiteral) {
89040
- let parsed;
89041
- try {
89042
- parsed = JSON.parse(arrayLiteral);
89043
- } catch {
89044
- let normalized = arrayLiteral;
89045
- normalized = normalized.replace(/'((?:[^'\\]|\\.)*)'/g, (_match, inner) => {
89046
- const escaped = inner.replace(/\\'/g, "'").replace(/"/g, '\\"');
89047
- return `"${escaped}"`;
89048
- });
89049
- normalized = normalized.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":');
89050
- normalized = normalized.replace(/,(\s*[}\]])/g, "$1");
89051
- parsed = JSON.parse(normalized);
89066
+ function readStaticValue(node) {
89067
+ switch (node?.type) {
89068
+ case "Literal":
89069
+ if ("regex" in node || "bigint" in node) break;
89070
+ return node.value;
89071
+ case "UnaryExpression":
89072
+ return readSignedNumber(node);
89073
+ case "ArrayExpression":
89074
+ return node.elements.map(readStaticValue);
89075
+ case "ObjectExpression":
89076
+ return Object.fromEntries(node.properties.map(readStaticProperty));
89077
+ }
89078
+ throw new SyntaxError("Transcript values must be static literals");
89079
+ }
89080
+ function readSignedNumber(node) {
89081
+ if (node.operator !== "-" && node.operator !== "+" || node.argument.type !== "Literal" || typeof node.argument.value !== "number") {
89082
+ throw new SyntaxError("Transcript unary expressions must be signed numeric literals");
89083
+ }
89084
+ return node.operator === "-" ? -node.argument.value : node.argument.value;
89085
+ }
89086
+ function readStaticProperty(property) {
89087
+ if (property.type !== "Property" || property.kind !== "init" || property.method || property.shorthand || property.computed) {
89088
+ throw new SyntaxError("Transcript properties must be static data");
89089
+ }
89090
+ const key2 = property.key.type === "Identifier" ? property.key.name : readStaticValue(property.key);
89091
+ if (typeof key2 !== "string" && typeof key2 !== "number") {
89092
+ throw new SyntaxError("Transcript property keys must be names or literals");
89052
89093
  }
89094
+ return [key2, readStaticValue(property.value)];
89095
+ }
89096
+ function parseTranscriptArray(parsed) {
89053
89097
  if (!Array.isArray(parsed)) {
89054
89098
  return [];
89055
89099
  }
89056
89100
  const words = [];
89057
- for (const item of parsed) {
89058
- if (item !== null && typeof item === "object" && typeof item.text === "string" && typeof item.start === "number" && typeof item.end === "number") {
89059
- const entry2 = item;
89101
+ const items = parsed;
89102
+ for (const item of items) {
89103
+ if (item !== null && typeof item === "object" && "text" in item && typeof item.text === "string" && "start" in item && typeof item.start === "number" && "end" in item && typeof item.end === "number") {
89060
89104
  words.push({
89061
- ...typeof entry2.id === "string" ? { id: entry2.id } : {},
89062
- text: entry2.text,
89063
- start: entry2.start,
89064
- end: entry2.end
89105
+ ..."id" in item && typeof item.id === "string" ? { id: item.id } : {},
89106
+ text: item.text,
89107
+ start: item.start,
89108
+ end: item.end
89065
89109
  });
89066
89110
  }
89067
89111
  }
@@ -89517,7 +89561,14 @@ function useToast() {
89517
89561
  import { useCallback as useCallback160, useEffect as useEffect132, useRef as useRef166, useState as useState132 } from "react";
89518
89562
 
89519
89563
  // src/utils/studioUrlState.ts
89520
- var VALID_TABS = ["layers", "design", "renders", "slideshow", "variables"];
89564
+ var VALID_TABS = [
89565
+ "layers",
89566
+ "design",
89567
+ "renders",
89568
+ "slideshow",
89569
+ "variables",
89570
+ "motionBlur"
89571
+ ];
89521
89572
  function resolveMasterCompositionPath(compositions) {
89522
89573
  if (compositions.includes("index.html")) return "index.html";
89523
89574
  return compositions.find((p) => p.endsWith(".html")) ?? null;
@@ -91778,7 +91829,7 @@ function useGestureTrailOverlay({
91778
91829
  }
91779
91830
 
91780
91831
  // src/hooks/useStudioSidePanels.tsx
91781
- import { useMemo as useMemo88 } from "react";
91832
+ import { useMemo as useMemo89 } from "react";
91782
91833
 
91783
91834
  // src/components/StudioLeftPanels.tsx
91784
91835
  import { memo as memo51, useCallback as useCallback172 } from "react";
@@ -94413,7 +94464,7 @@ function CodeBody({
94413
94464
  }
94414
94465
 
94415
94466
  // src/components/StudioRightPanels.tsx
94416
- import { memo as memo61, useCallback as useCallback189 } from "react";
94467
+ import { memo as memo62, useCallback as useCallback190 } from "react";
94417
94468
 
94418
94469
  // src/components/editor/LayersPanel.tsx
94419
94470
  import { memo as memo52, useState as useState148, useCallback as useCallback174, useEffect as useEffect146, useRef as useRef179 } from "react";
@@ -98574,8 +98625,175 @@ var VariablesPanel = memo60(function VariablesPanel2({
98574
98625
  ] });
98575
98626
  });
98576
98627
 
98628
+ // src/components/panels/MotionBlurPanel.tsx
98629
+ import { memo as memo61, useCallback as useCallback185, useEffect as useEffect154, useMemo as useMemo87, useReducer as useReducer2, useRef as useRef185, useState as useState161 } from "react";
98630
+
98631
+ // src/components/panels/motionBlurPanelModel.ts
98632
+ import {
98633
+ MOTION_BLUR_ATTR,
98634
+ MOTION_BLUR_LIMITS,
98635
+ motionBlurAttributeEdits,
98636
+ readMotionBlurSettings
98637
+ } from "@hyperframes/core/motion-blur-settings";
98638
+ function findCompositionRoot(roots) {
98639
+ const root = roots.find((el) => "data-composition-id" in el.attributes) ?? roots[0];
98640
+ return root ? { id: root.id, attributes: root.attributes } : null;
98641
+ }
98642
+ var settingsOf = (attributes) => readMotionBlurSettings((name) => attributes[name] ?? null);
98643
+ function changedEdits(attributes, next) {
98644
+ return Object.entries(motionBlurAttributeEdits(next)).filter(([name, value]) => (attributes[name] ?? null) !== value).map(([name, value]) => ({ name, value }));
98645
+ }
98646
+ function withEdits(attributes, edits) {
98647
+ const next = { ...attributes };
98648
+ for (const { name, value } of edits) {
98649
+ if (value === null) delete next[name];
98650
+ else next[name] = value;
98651
+ }
98652
+ return next;
98653
+ }
98654
+ function readPreviewRootAttributes(iframe) {
98655
+ try {
98656
+ const root = iframe?.contentDocument?.querySelector("[data-composition-id]");
98657
+ if (!root) return null;
98658
+ const found = {};
98659
+ for (const name of Object.values(MOTION_BLUR_ATTR)) {
98660
+ const value = root.getAttribute(name);
98661
+ if (value !== null) found[name] = value;
98662
+ }
98663
+ return found;
98664
+ } catch {
98665
+ return null;
98666
+ }
98667
+ }
98668
+ var sameAttributes = (a, b) => a === b || a !== null && b !== null && Object.keys({ ...a, ...b }).every((name) => a[name] === b[name]);
98669
+ var BLUR_LEVELS = [
98670
+ { level: "off", label: "Off" },
98671
+ { level: "subtle", label: "Subtle" },
98672
+ { level: "strong", label: "Strong" }
98673
+ ];
98674
+ var LEVEL_SETTINGS = {
98675
+ subtle: { shutterDeg: MOTION_BLUR_LIMITS.shutterDeg.default, samples: null },
98676
+ strong: {
98677
+ shutterDeg: MOTION_BLUR_LIMITS.shutterDeg.max,
98678
+ samples: MOTION_BLUR_LIMITS.samples.max
98679
+ }
98680
+ };
98681
+ var STRONG_ABOVE = (LEVEL_SETTINGS.subtle.shutterDeg + LEVEL_SETTINGS.strong.shutterDeg) / 2;
98682
+ function levelOf(settings) {
98683
+ if (!settings.enabled) return "off";
98684
+ return settings.shutterDeg > STRONG_ABOVE ? "strong" : "subtle";
98685
+ }
98686
+ function patchForLevel(level) {
98687
+ return level === "off" ? { enabled: false } : { enabled: true, ...LEVEL_SETTINGS[level] };
98688
+ }
98689
+
98690
+ // src/components/panels/MotionBlurPanel.tsx
98691
+ import { jsx as jsx244, jsxs as jsxs193 } from "react/jsx-runtime";
98692
+ var POLL_MS2 = 500;
98693
+ var SETTLE_MS = 1500;
98694
+ var NothingToChange = class extends Error {
98695
+ };
98696
+ function useLivePreviewAttributes(previewIframeRef, refreshKey) {
98697
+ const [live, setLive] = useState161(null);
98698
+ const holdUntil = useRef185(0);
98699
+ useEffect154(() => {
98700
+ const read2 = () => {
98701
+ const found = readPreviewRootAttributes(previewIframeRef.current);
98702
+ if (found && Date.now() >= holdUntil.current) {
98703
+ setLive((current) => sameAttributes(current, found) ? current : found);
98704
+ }
98705
+ };
98706
+ read2();
98707
+ const timer = setInterval(read2, POLL_MS2);
98708
+ return () => clearInterval(timer);
98709
+ }, [previewIframeRef, refreshKey]);
98710
+ const hold = useCallback185((next) => {
98711
+ holdUntil.current = Date.now() + SETTLE_MS;
98712
+ setLive(next);
98713
+ }, []);
98714
+ return [live, hold];
98715
+ }
98716
+ var MotionBlurPanel = memo61(function MotionBlurPanel2({
98717
+ sdkSession,
98718
+ publishSdkSession,
98719
+ reloadPreview,
98720
+ recordEdit
98721
+ }) {
98722
+ const { activeCompPath, previewIframeRef, showToast } = useStudioShellContext();
98723
+ const { refreshKey } = useStudioPlaybackContext();
98724
+ const { readProjectFile, writeProjectFile, compositions } = useFileManagerContext();
98725
+ const [, rerender] = useReducer2((n) => n + 1, 0);
98726
+ const [live, hold] = useLivePreviewAttributes(previewIframeRef, refreshKey);
98727
+ useEffect154(() => sdkSession?.on("change", rerender), [sdkSession]);
98728
+ const root = sdkSession ? findCompositionRoot(sdkSession.getRootElements()) : null;
98729
+ const attributes = live ?? root?.attributes ?? null;
98730
+ const settings = useMemo87(() => attributes ? settingsOf(attributes) : null, [attributes]);
98731
+ const isMain = activeCompPath == null || activeCompPath === resolveMasterCompositionPath(compositions);
98732
+ const persist2 = useVariablesPersist({
98733
+ sdkSession,
98734
+ activeCompPath,
98735
+ readProjectFile,
98736
+ writeProjectFile,
98737
+ recordEdit,
98738
+ reloadPreview,
98739
+ publishSdkSession
98740
+ });
98741
+ const apply = useCallback185(
98742
+ async (patch) => {
98743
+ if (!attributes) return;
98744
+ let applied = [];
98745
+ try {
98746
+ await persist2("Edit motion blur", (session) => {
98747
+ const target = findCompositionRoot(session.getRootElements());
98748
+ if (!target) return;
98749
+ const next = { ...settingsOf(target.attributes), ...patch };
98750
+ applied = changedEdits(target.attributes, next);
98751
+ if (applied.length === 0) throw new NothingToChange();
98752
+ for (const e of applied) {
98753
+ session.dispatch({
98754
+ type: "setAttribute",
98755
+ target: target.id,
98756
+ name: e.name,
98757
+ value: e.value
98758
+ });
98759
+ }
98760
+ });
98761
+ hold((current2) => withEdits(current2 ?? attributes, applied));
98762
+ rerender();
98763
+ } catch (err) {
98764
+ if (err instanceof NothingToChange) return;
98765
+ showToast(err instanceof Error ? err.message : String(err), "error");
98766
+ }
98767
+ },
98768
+ [attributes, persist2, hold, showToast]
98769
+ );
98770
+ if (!settings) {
98771
+ return /* @__PURE__ */ jsx244("p", { className: "p-3 text-[10px] leading-relaxed text-neutral-500", children: "Open a composition to set up motion blur." });
98772
+ }
98773
+ const locked = !isMain;
98774
+ const current = locked ? null : levelOf(settings);
98775
+ return /* @__PURE__ */ jsxs193("div", { className: "flex h-full flex-col gap-3 overflow-y-auto p-3 text-[11px] text-neutral-300", children: [
98776
+ /* @__PURE__ */ jsx244("span", { className: "text-[12px] font-medium text-neutral-100", children: "Motion blur" }),
98777
+ /* @__PURE__ */ jsx244("div", { role: "radiogroup", "aria-label": "Motion blur", className: "flex gap-1.5", children: BLUR_LEVELS.map(({ level, label }) => /* @__PURE__ */ jsx244(
98778
+ Button,
98779
+ {
98780
+ role: "radio",
98781
+ "aria-checked": current === level,
98782
+ size: "sm",
98783
+ variant: current === level ? "secondary" : "ghost",
98784
+ className: "flex-1",
98785
+ disabled: locked,
98786
+ onClick: () => void apply(patchForLevel(level)),
98787
+ children: label
98788
+ },
98789
+ level
98790
+ )) }),
98791
+ locked ? /* @__PURE__ */ jsx244("p", { className: "text-[10px] leading-relaxed text-amber-400/90", children: "Motion blur is read from the main composition. Open it to change this setting." }) : null
98792
+ ] });
98793
+ });
98794
+
98577
98795
  // src/hooks/useSlideshowPersist.ts
98578
- import { useCallback as useCallback185 } from "react";
98796
+ import { useCallback as useCallback186 } from "react";
98579
98797
 
98580
98798
  // src/utils/setSlideshowManifest.ts
98581
98799
  import {
@@ -98632,7 +98850,7 @@ function useSlideshowPersist({
98632
98850
  publishSdkSession,
98633
98851
  coalesceKey
98634
98852
  }) {
98635
- return useCallback185(
98853
+ return useCallback186(
98636
98854
  async (manifest) => {
98637
98855
  if (!sdkSession) return;
98638
98856
  const path = activeCompPath ?? "index.html";
@@ -98665,15 +98883,15 @@ function useSlideshowPersist({
98665
98883
  }
98666
98884
 
98667
98885
  // src/hooks/useSlideshowTabState.ts
98668
- import { useMemo as useMemo87 } from "react";
98886
+ import { useMemo as useMemo88 } from "react";
98669
98887
  import { SLIDESHOW_ISLAND_TYPE as SLIDESHOW_ISLAND_TYPE2, slideshowIslandRegex as slideshowIslandRegex2 } from "@hyperframes/core/slideshow";
98670
98888
  function useSlideshowTabState(params) {
98671
98889
  const { editingFileContent, previewIframeRef, refreshKey, slideshowVisible } = params;
98672
- const isSlideshowComposition = useMemo87(() => {
98890
+ const isSlideshowComposition = useMemo88(() => {
98673
98891
  if (!editingFileContent || !editingFileContent.includes(SLIDESHOW_ISLAND_TYPE2)) return false;
98674
98892
  return slideshowIslandRegex2("i").test(editingFileContent);
98675
98893
  }, [editingFileContent]);
98676
- const slideshowScenes = useMemo87(() => {
98894
+ const slideshowScenes = useMemo88(() => {
98677
98895
  try {
98678
98896
  const win = previewIframeRef.current?.contentWindow;
98679
98897
  return (win?.__clipManifest?.scenes ?? []).map((s) => ({
@@ -98690,8 +98908,8 @@ function useSlideshowTabState(params) {
98690
98908
  }
98691
98909
 
98692
98910
  // src/components/DesignPanelPromoteProvider.tsx
98693
- import { useCallback as useCallback186 } from "react";
98694
- import { jsx as jsx244 } from "react/jsx-runtime";
98911
+ import { useCallback as useCallback187 } from "react";
98912
+ import { jsx as jsx245 } from "react/jsx-runtime";
98695
98913
  function DesignPanelPromoteProvider({
98696
98914
  selection,
98697
98915
  projectId,
@@ -98709,7 +98927,7 @@ function DesignPanelPromoteProvider({
98709
98927
  publishSdkSession: handle.publish,
98710
98928
  activeCompPath: targetPath
98711
98929
  });
98712
- const persist2 = useCallback186(
98930
+ const persist2 = useCallback187(
98713
98931
  async (label, mutate) => {
98714
98932
  const committed = await rawPersist(label, mutate);
98715
98933
  if (committed) forceReloadSharedSdkSession?.();
@@ -98717,11 +98935,11 @@ function DesignPanelPromoteProvider({
98717
98935
  },
98718
98936
  [rawPersist, forceReloadSharedSdkSession]
98719
98937
  );
98720
- const handlePersistError = useCallback186(
98938
+ const handlePersistError = useCallback187(
98721
98939
  (error) => showToast(getStudioSaveErrorMessage(error), "error"),
98722
98940
  [showToast]
98723
98941
  );
98724
- return /* @__PURE__ */ jsx244(
98942
+ return /* @__PURE__ */ jsx245(
98725
98943
  VariablePromoteProvider,
98726
98944
  {
98727
98945
  session: handle.session,
@@ -98734,7 +98952,7 @@ function DesignPanelPromoteProvider({
98734
98952
  }
98735
98953
 
98736
98954
  // src/hooks/useRemoveBackground.ts
98737
- import { useCallback as useCallback187, useEffect as useEffect154, useRef as useRef185 } from "react";
98955
+ import { useCallback as useCallback188, useEffect as useEffect155, useRef as useRef186 } from "react";
98738
98956
 
98739
98957
  // src/components/studioMediaJobs.ts
98740
98958
  var MEDIA_JOB_RECONNECT_TIMEOUT_MS = 15e3;
@@ -98840,14 +99058,14 @@ function waitForMediaJob(jobId, onProgress, signal) {
98840
99058
 
98841
99059
  // src/hooks/useRemoveBackground.ts
98842
99060
  function useRemoveBackground(projectId, refreshFileTree, showToast) {
98843
- const abortRef = useRef185(null);
98844
- useEffect154(
99061
+ const abortRef = useRef186(null);
99062
+ useEffect155(
98845
99063
  () => () => {
98846
99064
  abortRef.current?.abort();
98847
99065
  },
98848
99066
  []
98849
99067
  );
98850
- return useCallback187(
99068
+ return useCallback188(
98851
99069
  // fallow-ignore-next-line complexity
98852
99070
  async (inputPath, options) => {
98853
99071
  const response = await studioApiFetch(
@@ -98886,7 +99104,7 @@ function useRemoveBackground(projectId, refreshFileTree, showToast) {
98886
99104
  }
98887
99105
 
98888
99106
  // src/hooks/useApplyColorGradingScope.ts
98889
- import { useCallback as useCallback188 } from "react";
99107
+ import { useCallback as useCallback189 } from "react";
98890
99108
 
98891
99109
  // src/components/editor/colorGradingScopePatch.ts
98892
99110
  var MEDIA_TAG_RE = /<\s*(video|img)\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi;
@@ -98990,7 +99208,7 @@ function useApplyColorGradingScope(recordEdit, reloadPreview) {
98990
99208
  const { projectId, activeCompPath, showToast, waitForPendingDomEditSaves } = useStudioShellContext();
98991
99209
  const { domEditSelection } = useDomEditContext();
98992
99210
  const { readProjectFile, writeProjectFile, compositions } = useFileManagerContext();
98993
- return useCallback188(
99211
+ return useCallback189(
98994
99212
  async (scope, value) => applyColorGradingScopeUpdate({
98995
99213
  scope,
98996
99214
  value,
@@ -99026,9 +99244,9 @@ function useApplyColorGradingScope(recordEdit, reloadPreview) {
99026
99244
  }
99027
99245
 
99028
99246
  // src/components/StudioRightPanels.tsx
99029
- import { Fragment as Fragment66, jsx as jsx245, jsxs as jsxs193 } from "react/jsx-runtime";
99247
+ import { Fragment as Fragment66, jsx as jsx246, jsxs as jsxs194 } from "react/jsx-runtime";
99030
99248
  var seekToTime = (t2) => usePlayerStore.getState().requestSeek(t2);
99031
- var StudioRightPanels = memo61(function StudioRightPanels2({
99249
+ var StudioRightPanels = memo62(function StudioRightPanels2({
99032
99250
  activeBlockParams,
99033
99251
  onCloseBlockParams,
99034
99252
  onDismissBlockParams,
@@ -99138,11 +99356,11 @@ var StudioRightPanels = memo61(function StudioRightPanels2({
99138
99356
  useCaptionDesignFocus(captionEditMode);
99139
99357
  const handleApplyColorGradingScope = useApplyColorGradingScope(recordEdit, reloadPreview);
99140
99358
  const handleRemoveBackground = useRemoveBackground(projectId, refreshFileTree, showToast);
99141
- const setAttributeWhileDragging = useCallback189(
99359
+ const setAttributeWhileDragging = useCallback190(
99142
99360
  (attr, value) => handleDomAttributeLiveCommit(attr, value, void 0, { previewOnly: true }),
99143
99361
  [handleDomAttributeLiveCommit]
99144
99362
  );
99145
- const handleHideAllSelected = useCallback189(() => {
99363
+ const handleHideAllSelected = useCallback190(() => {
99146
99364
  if (!canHideSelections(domEditGroupSelections)) {
99147
99365
  showToast("Audio can't be hidden. Use the group's own controls", "info");
99148
99366
  return;
@@ -99151,11 +99369,11 @@ var StudioRightPanels = memo61(function StudioRightPanels2({
99151
99369
  const keys = timelineKeysForSelections(domEditGroupSelections, elements, activeCompPath);
99152
99370
  if (keys.length > 0) void onToggleElementHidden?.(keys, true);
99153
99371
  }, [domEditGroupSelections, showToast, activeCompPath, onToggleElementHidden]);
99154
- const convertToKeyframes = useCallback189(
99372
+ const convertToKeyframes = useCallback190(
99155
99373
  (animId, duration) => handleGsapConvertToKeyframes(animId, void 0, duration),
99156
99374
  [handleGsapConvertToKeyframes]
99157
99375
  );
99158
- const propertyPanel = /* @__PURE__ */ jsx245(
99376
+ const propertyPanel = /* @__PURE__ */ jsx246(
99159
99377
  DesignPanelPromoteProvider,
99160
99378
  {
99161
99379
  selection: domEditGroupSelections.length > 1 ? null : domEditSelection,
@@ -99167,7 +99385,7 @@ var StudioRightPanels = memo61(function StudioRightPanels2({
99167
99385
  recordEdit,
99168
99386
  reloadPreview,
99169
99387
  forceReloadSharedSdkSession: forceReloadSdkSession,
99170
- children: /* @__PURE__ */ jsx245(
99388
+ children: /* @__PURE__ */ jsx246(
99171
99389
  PropertyPanel,
99172
99390
  {
99173
99391
  projectId,
@@ -99238,9 +99456,9 @@ var StudioRightPanels = memo61(function StudioRightPanels2({
99238
99456
  );
99239
99457
  let designBody = propertyPanel;
99240
99458
  if (captionEditMode) {
99241
- designBody = /* @__PURE__ */ jsx245(CaptionPropertyPanel, { iframeRef: previewIframeRef });
99459
+ designBody = /* @__PURE__ */ jsx246(CaptionPropertyPanel, { iframeRef: previewIframeRef });
99242
99460
  } else if (activeBlockParams) {
99243
- designBody = /* @__PURE__ */ jsx245(
99461
+ designBody = /* @__PURE__ */ jsx246(
99244
99462
  BlockParamsPanel,
99245
99463
  {
99246
99464
  blockName: activeBlockParams.blockName,
@@ -99252,18 +99470,18 @@ var StudioRightPanels = memo61(function StudioRightPanels2({
99252
99470
  }
99253
99471
  );
99254
99472
  }
99255
- return /* @__PURE__ */ jsxs193(Fragment66, { children: [
99256
- /* @__PURE__ */ jsx245(Dock.Panel, { id: "design", children: designBody }),
99257
- /* @__PURE__ */ jsx245(Dock.Panel, { id: "layers", children: /* @__PURE__ */ jsx245(LayersPanel, {}) }),
99258
- /* @__PURE__ */ jsx245(
99473
+ return /* @__PURE__ */ jsxs194(Fragment66, { children: [
99474
+ /* @__PURE__ */ jsx246(Dock.Panel, { id: "design", children: designBody }),
99475
+ /* @__PURE__ */ jsx246(Dock.Panel, { id: "layers", children: /* @__PURE__ */ jsx246(LayersPanel, {}) }),
99476
+ /* @__PURE__ */ jsx246(
99259
99477
  Dock.Panel,
99260
99478
  {
99261
99479
  id: "renders",
99262
99480
  title: renderJobs.length > 0 ? `Renders (${renderJobs.length})` : void 0,
99263
- children: /* @__PURE__ */ jsx245(RenderQueuePanel, {})
99481
+ children: /* @__PURE__ */ jsx246(RenderQueuePanel, {})
99264
99482
  }
99265
99483
  ),
99266
- /* @__PURE__ */ jsx245(Dock.Panel, { id: "variables", children: /* @__PURE__ */ jsx245(
99484
+ /* @__PURE__ */ jsx246(Dock.Panel, { id: "variables", children: /* @__PURE__ */ jsx246(
99267
99485
  VariablesPanel,
99268
99486
  {
99269
99487
  sdkSession,
@@ -99272,7 +99490,16 @@ var StudioRightPanels = memo61(function StudioRightPanels2({
99272
99490
  recordEdit
99273
99491
  }
99274
99492
  ) }),
99275
- /* @__PURE__ */ jsx245(Dock.Panel, { id: "slideshow", children: /* @__PURE__ */ jsx245(
99493
+ /* @__PURE__ */ jsx246(Dock.Panel, { id: "motionBlur", children: /* @__PURE__ */ jsx246(
99494
+ MotionBlurPanel,
99495
+ {
99496
+ sdkSession,
99497
+ publishSdkSession,
99498
+ reloadPreview,
99499
+ recordEdit
99500
+ }
99501
+ ) }),
99502
+ /* @__PURE__ */ jsx246(Dock.Panel, { id: "slideshow", children: /* @__PURE__ */ jsx246(
99276
99503
  SlideshowPanel,
99277
99504
  {
99278
99505
  scenes: slideshowScenes,
@@ -99284,21 +99511,21 @@ var StudioRightPanels = memo61(function StudioRightPanels2({
99284
99511
  });
99285
99512
 
99286
99513
  // src/hooks/useStudioSidePanels.tsx
99287
- import { Fragment as Fragment67, jsx as jsx246, jsxs as jsxs194 } from "react/jsx-runtime";
99514
+ import { Fragment as Fragment67, jsx as jsx247, jsxs as jsxs195 } from "react/jsx-runtime";
99288
99515
  function useStudioSidePanels(left, right, projectId) {
99289
99516
  const stableLeft = useStableHandlers(left, projectId);
99290
99517
  const stableRight = useStableHandlers(right, projectId);
99291
- return useMemo88(
99292
- () => /* @__PURE__ */ jsxs194(Fragment67, { children: [
99293
- /* @__PURE__ */ jsx246(StudioLeftPanels, { ...stableLeft }),
99294
- /* @__PURE__ */ jsx246(StudioRightPanels, { ...stableRight })
99518
+ return useMemo89(
99519
+ () => /* @__PURE__ */ jsxs195(Fragment67, { children: [
99520
+ /* @__PURE__ */ jsx247(StudioLeftPanels, { ...stableLeft }),
99521
+ /* @__PURE__ */ jsx247(StudioRightPanels, { ...stableRight })
99295
99522
  ] }),
99296
99523
  [stableLeft, stableRight]
99297
99524
  );
99298
99525
  }
99299
99526
 
99300
99527
  // src/components/TimelineToolbar.tsx
99301
- import { memo as memo62, useEffect as useEffect156, useRef as useRef186 } from "react";
99528
+ import { memo as memo63, useEffect as useEffect157, useRef as useRef187 } from "react";
99302
99529
  import {
99303
99530
  ArrowsOutLineHorizontal,
99304
99531
  Image as Image3,
@@ -99308,9 +99535,9 @@ import {
99308
99535
  } from "@phosphor-icons/react";
99309
99536
 
99310
99537
  // src/hooks/useKeyframeKeyboard.ts
99311
- import { useEffect as useEffect155, useCallback as useCallback190 } from "react";
99538
+ import { useEffect as useEffect156, useCallback as useCallback191 } from "react";
99312
99539
  function useKeyframeKeyboard({ enabled, onAddKeyframe }) {
99313
- const handler = useCallback190(
99540
+ const handler = useCallback191(
99314
99541
  (e) => {
99315
99542
  if (!enabled || !onAddKeyframe) return;
99316
99543
  if (isTypingTarget(document.activeElement)) return;
@@ -99322,7 +99549,7 @@ function useKeyframeKeyboard({ enabled, onAddKeyframe }) {
99322
99549
  },
99323
99550
  [enabled, onAddKeyframe]
99324
99551
  );
99325
- useEffect155(() => {
99552
+ useEffect156(() => {
99326
99553
  if (!enabled) return;
99327
99554
  window.addEventListener("keydown", handler, { capture: true });
99328
99555
  return () => window.removeEventListener("keydown", handler, { capture: true });
@@ -99330,9 +99557,9 @@ function useKeyframeKeyboard({ enabled, onAddKeyframe }) {
99330
99557
  }
99331
99558
 
99332
99559
  // src/components/icons/AudioMetersIcon.tsx
99333
- import { jsx as jsx247 } from "react/jsx-runtime";
99560
+ import { jsx as jsx248 } from "react/jsx-runtime";
99334
99561
  function AudioMetersIcon({ size = 16 }) {
99335
- return /* @__PURE__ */ jsx247(
99562
+ return /* @__PURE__ */ jsx248(
99336
99563
  "svg",
99337
99564
  {
99338
99565
  width: size,
@@ -99343,15 +99570,15 @@ function AudioMetersIcon({ size = 16 }) {
99343
99570
  strokeWidth: 1.5,
99344
99571
  strokeLinecap: "round",
99345
99572
  "aria-hidden": "true",
99346
- children: /* @__PURE__ */ jsx247("path", { d: "M2.5 13h2m-2-3h2M7 13h2m-2-3h2m-2-3h2m-2-3h2M11.5 13h2m-2-3h2m-2-3h2" })
99573
+ children: /* @__PURE__ */ jsx248("path", { d: "M2.5 13h2m-2-3h2M7 13h2m-2-3h2m-2-3h2m-2-3h2M11.5 13h2m-2-3h2m-2-3h2" })
99347
99574
  }
99348
99575
  );
99349
99576
  }
99350
99577
 
99351
99578
  // src/components/icons/RippleEditIcon.tsx
99352
- import { jsx as jsx248, jsxs as jsxs195 } from "react/jsx-runtime";
99579
+ import { jsx as jsx249, jsxs as jsxs196 } from "react/jsx-runtime";
99353
99580
  function RippleEditIcon({ size = 16 }) {
99354
- return /* @__PURE__ */ jsxs195(
99581
+ return /* @__PURE__ */ jsxs196(
99355
99582
  "svg",
99356
99583
  {
99357
99584
  width: size,
@@ -99364,20 +99591,20 @@ function RippleEditIcon({ size = 16 }) {
99364
99591
  strokeLinejoin: "round",
99365
99592
  "aria-hidden": "true",
99366
99593
  children: [
99367
- /* @__PURE__ */ jsx248("rect", { x: "1.5", y: "8", width: "4", height: "5", rx: "0.5" }),
99368
- /* @__PURE__ */ jsx248("rect", { x: "10.5", y: "8", width: "4", height: "5", rx: "0.5" }),
99369
- /* @__PURE__ */ jsx248("path", { d: "M13 4H6m2-2L6 4l2 2" })
99594
+ /* @__PURE__ */ jsx249("rect", { x: "1.5", y: "8", width: "4", height: "5", rx: "0.5" }),
99595
+ /* @__PURE__ */ jsx249("rect", { x: "10.5", y: "8", width: "4", height: "5", rx: "0.5" }),
99596
+ /* @__PURE__ */ jsx249("path", { d: "M13 4H6m2-2L6 4l2 2" })
99370
99597
  ]
99371
99598
  }
99372
99599
  );
99373
99600
  }
99374
99601
 
99375
99602
  // src/components/TimelineHistoryButtons.tsx
99376
- import { Fragment as Fragment68, jsx as jsx249, jsxs as jsxs196 } from "react/jsx-runtime";
99603
+ import { Fragment as Fragment68, jsx as jsx250, jsxs as jsxs197 } from "react/jsx-runtime";
99377
99604
  function HistoryButton({ action, can, label, onClick }) {
99378
99605
  const Icon = action === "undo" ? RotateCcw : RotateCw;
99379
99606
  const enabled = Boolean(onClick) && can;
99380
- return /* @__PURE__ */ jsx249(Tooltip, { label: historyTooltipLabel(action, label), children: /* @__PURE__ */ jsx249(
99607
+ return /* @__PURE__ */ jsx250(Tooltip, { label: historyTooltipLabel(action, label), children: /* @__PURE__ */ jsx250(
99381
99608
  "button",
99382
99609
  {
99383
99610
  type: "button",
@@ -99388,7 +99615,7 @@ function HistoryButton({ action, can, label, onClick }) {
99388
99615
  trackStudioEvent("toolbar_action", { action });
99389
99616
  void onClick?.();
99390
99617
  },
99391
- children: /* @__PURE__ */ jsx249(Icon, { size: 16 })
99618
+ children: /* @__PURE__ */ jsx250(Icon, { size: 16 })
99392
99619
  }
99393
99620
  ) });
99394
99621
  }
@@ -99396,8 +99623,8 @@ function TimelineHistoryButtons(props) {
99396
99623
  const shell = useStudioShellContextOptional();
99397
99624
  const onUndo = props.onUndo ?? shell?.handleUndo;
99398
99625
  const onRedo = props.onRedo ?? shell?.handleRedo;
99399
- return /* @__PURE__ */ jsxs196(Fragment68, { children: [
99400
- /* @__PURE__ */ jsx249(
99626
+ return /* @__PURE__ */ jsxs197(Fragment68, { children: [
99627
+ /* @__PURE__ */ jsx250(
99401
99628
  HistoryButton,
99402
99629
  {
99403
99630
  action: "undo",
@@ -99406,7 +99633,7 @@ function TimelineHistoryButtons(props) {
99406
99633
  onClick: onUndo
99407
99634
  }
99408
99635
  ),
99409
- /* @__PURE__ */ jsx249(
99636
+ /* @__PURE__ */ jsx250(
99410
99637
  HistoryButton,
99411
99638
  {
99412
99639
  action: "redo",
@@ -99420,9 +99647,9 @@ function TimelineHistoryButtons(props) {
99420
99647
 
99421
99648
  // src/components/TimelineToolPicker.tsx
99422
99649
  import { ArrowLineLeft, ArrowLineRight, CaretDown } from "@phosphor-icons/react";
99423
- import { jsx as jsx250, jsxs as jsxs197 } from "react/jsx-runtime";
99650
+ import { jsx as jsx251, jsxs as jsxs198 } from "react/jsx-runtime";
99424
99651
  function SelectIcon() {
99425
- return /* @__PURE__ */ jsx250("svg", { width: "16", height: "16", viewBox: "0 0 12 12", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx250("path", { d: "M2 0.5L10 6L6.5 6.5L8.5 11L6.5 11.5L4.5 7L2 9Z" }) });
99652
+ return /* @__PURE__ */ jsx251("svg", { width: "16", height: "16", viewBox: "0 0 12 12", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx251("path", { d: "M2 0.5L10 6L6.5 6.5L8.5 11L6.5 11.5L4.5 7L2 9Z" }) });
99426
99653
  }
99427
99654
  function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
99428
99655
  const activeTool = usePlayerStore((s) => s.activeTool);
@@ -99431,14 +99658,14 @@ function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
99431
99658
  {
99432
99659
  label: "Select",
99433
99660
  shortcut: "V",
99434
- icon: /* @__PURE__ */ jsx250(SelectIcon, {}),
99661
+ icon: /* @__PURE__ */ jsx251(SelectIcon, {}),
99435
99662
  active: activeTool === "select",
99436
99663
  run: () => store().setActiveTool("select")
99437
99664
  },
99438
99665
  {
99439
99666
  label: "Split",
99440
99667
  shortcut: "B",
99441
- icon: /* @__PURE__ */ jsx250(Scissors, { size: 16 }),
99668
+ icon: /* @__PURE__ */ jsx251(Scissors, { size: 16 }),
99442
99669
  active: activeTool === "razor",
99443
99670
  run: () => store().setActiveTool("razor")
99444
99671
  }
@@ -99448,25 +99675,25 @@ function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
99448
99675
  {
99449
99676
  label: "Select leftward",
99450
99677
  shortcut: "[",
99451
- icon: /* @__PURE__ */ jsx250(ArrowLineLeft, { size: 16, "aria-hidden": "true" }),
99678
+ icon: /* @__PURE__ */ jsx251(ArrowLineLeft, { size: 16, "aria-hidden": "true" }),
99452
99679
  active: false,
99453
99680
  run: () => store().selectLeftward()
99454
99681
  },
99455
99682
  {
99456
99683
  label: "Select rightward",
99457
99684
  shortcut: "]",
99458
- icon: /* @__PURE__ */ jsx250(ArrowLineRight, { size: 16, "aria-hidden": "true" }),
99685
+ icon: /* @__PURE__ */ jsx251(ArrowLineRight, { size: 16, "aria-hidden": "true" }),
99459
99686
  active: false,
99460
99687
  run: () => store().selectRightward()
99461
99688
  }
99462
99689
  );
99463
99690
  }
99464
99691
  const current = rows.find((row) => row.active) ?? rows[0];
99465
- return /* @__PURE__ */ jsx250(
99692
+ return /* @__PURE__ */ jsx251(
99466
99693
  Menu,
99467
99694
  {
99468
99695
  "aria-label": "Timeline tools",
99469
- trigger: /* @__PURE__ */ jsxs197(
99696
+ trigger: /* @__PURE__ */ jsxs198(
99470
99697
  "button",
99471
99698
  {
99472
99699
  type: "button",
@@ -99474,18 +99701,18 @@ function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
99474
99701
  className: cn(flatIdle, "gap-0.5"),
99475
99702
  children: [
99476
99703
  current.icon,
99477
- /* @__PURE__ */ jsx250(CaretDown, { size: 10, weight: "bold", "aria-hidden": "true" })
99704
+ /* @__PURE__ */ jsx251(CaretDown, { size: 10, weight: "bold", "aria-hidden": "true" })
99478
99705
  ]
99479
99706
  }
99480
99707
  ),
99481
- children: rows.map((row) => /* @__PURE__ */ jsx250(
99708
+ children: rows.map((row) => /* @__PURE__ */ jsx251(
99482
99709
  MenuItem,
99483
99710
  {
99484
99711
  shortcut: row.shortcut,
99485
99712
  onClick: row.run,
99486
99713
  "data-active": row.active || void 0,
99487
99714
  className: row.active ? "bg-press text-text-0" : void 0,
99488
- children: /* @__PURE__ */ jsxs197("span", { className: "flex items-center gap-2", children: [
99715
+ children: /* @__PURE__ */ jsxs198("span", { className: "flex items-center gap-2", children: [
99489
99716
  row.icon,
99490
99717
  row.label
99491
99718
  ] })
@@ -99497,17 +99724,17 @@ function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
99497
99724
  }
99498
99725
 
99499
99726
  // src/components/LinkedSelectionToggle.tsx
99500
- import { useCallback as useCallback191, useState as useState161 } from "react";
99727
+ import { useCallback as useCallback192, useState as useState162 } from "react";
99501
99728
  import { createPortal as createPortal15 } from "react-dom";
99502
99729
  import { LinkSimple } from "@phosphor-icons/react";
99503
- import { Fragment as Fragment69, jsx as jsx251, jsxs as jsxs198 } from "react/jsx-runtime";
99730
+ import { Fragment as Fragment69, jsx as jsx252, jsxs as jsxs199 } from "react/jsx-runtime";
99504
99731
  function SyncIndicatorMenu({ x, y, onClose }) {
99505
99732
  const menuRef = useContextMenuDismiss(onClose);
99506
99733
  useMenuKeyboardNav(menuRef);
99507
99734
  const visible = useLinkedClipPreferences((s) => s.syncIndicatorsVisible);
99508
99735
  const setVisible = useLinkedClipPreferences((s) => s.setSyncIndicatorsVisible);
99509
99736
  return createPortal15(
99510
- /* @__PURE__ */ jsx251(
99737
+ /* @__PURE__ */ jsx252(
99511
99738
  "div",
99512
99739
  {
99513
99740
  ref: menuRef,
@@ -99515,7 +99742,7 @@ function SyncIndicatorMenu({ x, y, onClose }) {
99515
99742
  "aria-label": "Linked clips",
99516
99743
  className: `${menuClasses.panel} fixed z-200 min-w-[200px]`,
99517
99744
  style: { left: x, top: y },
99518
- children: /* @__PURE__ */ jsxs198(
99745
+ children: /* @__PURE__ */ jsxs199(
99519
99746
  "button",
99520
99747
  {
99521
99748
  type: "button",
@@ -99527,7 +99754,7 @@ function SyncIndicatorMenu({ x, y, onClose }) {
99527
99754
  onClose();
99528
99755
  },
99529
99756
  children: [
99530
- /* @__PURE__ */ jsx251("span", { className: "w-3 text-studio-accent", children: visible ? "\u2713" : "" }),
99757
+ /* @__PURE__ */ jsx252("span", { className: "w-3 text-studio-accent", children: visible ? "\u2713" : "" }),
99531
99758
  "Show out-of-sync indicators"
99532
99759
  ]
99533
99760
  }
@@ -99540,14 +99767,14 @@ function SyncIndicatorMenu({ x, y, onClose }) {
99540
99767
  function LinkedSelectionToggle() {
99541
99768
  const linkedSelection = useLinkedClipPreferences((s) => s.linkedSelection);
99542
99769
  const setLinkedSelection = useLinkedClipPreferences((s) => s.setLinkedSelection);
99543
- const [menuAt, setMenuAt] = useState161(null);
99544
- const closeMenu = useCallback191(() => setMenuAt(null), []);
99545
- return /* @__PURE__ */ jsxs198(Fragment69, { children: [
99546
- /* @__PURE__ */ jsx251(
99770
+ const [menuAt, setMenuAt] = useState162(null);
99771
+ const closeMenu = useCallback192(() => setMenuAt(null), []);
99772
+ return /* @__PURE__ */ jsxs199(Fragment69, { children: [
99773
+ /* @__PURE__ */ jsx252(
99547
99774
  Tooltip,
99548
99775
  {
99549
99776
  label: linkedSelection ? "Linked Selection on: a click selects both halves (\u2325-click for one)" : "Linked Selection off: a click selects one clip",
99550
- children: /* @__PURE__ */ jsx251(
99777
+ children: /* @__PURE__ */ jsx252(
99551
99778
  "button",
99552
99779
  {
99553
99780
  type: "button",
@@ -99559,17 +99786,17 @@ function LinkedSelectionToggle() {
99559
99786
  "aria-label": "Linked Selection",
99560
99787
  "aria-pressed": linkedSelection,
99561
99788
  className: linkedSelection ? flatActive : flatIdle,
99562
- children: /* @__PURE__ */ jsx251(LinkSimple, { size: 16, weight: "bold", "aria-hidden": "true" })
99789
+ children: /* @__PURE__ */ jsx252(LinkSimple, { size: 16, weight: "bold", "aria-hidden": "true" })
99563
99790
  }
99564
99791
  )
99565
99792
  }
99566
99793
  ),
99567
- menuAt && /* @__PURE__ */ jsx251(SyncIndicatorMenu, { x: menuAt.x, y: menuAt.y, onClose: closeMenu })
99794
+ menuAt && /* @__PURE__ */ jsx252(SyncIndicatorMenu, { x: menuAt.x, y: menuAt.y, onClose: closeMenu })
99568
99795
  ] });
99569
99796
  }
99570
99797
 
99571
99798
  // src/components/TimelineToolbar.tsx
99572
- import { Fragment as Fragment70, jsx as jsx252, jsxs as jsxs199 } from "react/jsx-runtime";
99799
+ import { Fragment as Fragment70, jsx as jsx253, jsxs as jsxs200 } from "react/jsx-runtime";
99573
99800
  var NO_KEYFRAME_TOGGLE = {
99574
99801
  state: "none",
99575
99802
  isMotionPath: false,
@@ -99613,7 +99840,7 @@ function useKeyframeToggle(session) {
99613
99840
  const currentTime = usePlayerStore((s) => s.currentTime);
99614
99841
  const selectedElementId = usePlayerStore((s) => s.selectedElementId);
99615
99842
  const elements = usePlayerStore((s) => s.elements);
99616
- const sessionRef = useRef186(session);
99843
+ const sessionRef = useRef187(session);
99617
99844
  sessionRef.current = session;
99618
99845
  const onToggle = useEnableKeyframes(
99619
99846
  sessionRef
@@ -99626,7 +99853,7 @@ function useKeyframeToggle(session) {
99626
99853
  onToggle: session?.domEditSelection && !toggleState.pathEndpoint ? onToggle : void 0
99627
99854
  };
99628
99855
  }
99629
- var TimelineToolbar = memo62(function TimelineToolbar2({
99856
+ var TimelineToolbar = memo63(function TimelineToolbar2({
99630
99857
  domEditSession,
99631
99858
  onSplitElement,
99632
99859
  splitShortcut = SPLIT_SHORTCUT_HINT,
@@ -99668,10 +99895,10 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99668
99895
  enabled: showKeyframes && Boolean(onToggleKeyframe),
99669
99896
  onAddKeyframe: onToggleKeyframe
99670
99897
  });
99671
- useEffect156(() => {
99898
+ useEffect157(() => {
99672
99899
  if (!showKeyframes) setAutoKeyframeEnabled(false);
99673
99900
  }, [showKeyframes, setAutoKeyframeEnabled]);
99674
- useEffect156(() => {
99901
+ useEffect157(() => {
99675
99902
  const onKeyDown = (e) => {
99676
99903
  if (e.key !== "n" && e.key !== "N") return;
99677
99904
  if (e.metaKey || e.ctrlKey || e.altKey) return;
@@ -99682,13 +99909,13 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99682
99909
  window.addEventListener("keydown", onKeyDown);
99683
99910
  return () => window.removeEventListener("keydown", onKeyDown);
99684
99911
  }, []);
99685
- return /* @__PURE__ */ jsx252("div", { className: "border-b border-neutral-800/60", children: /* @__PURE__ */ jsxs199("div", { className: "flex items-center justify-between px-2 py-0.5", children: [
99686
- /* @__PURE__ */ jsxs199("div", { className: "flex items-center gap-0.5", children: [
99687
- showHistory && /* @__PURE__ */ jsx252(TimelineHistoryButtons, { ...history2 }),
99688
- /* @__PURE__ */ jsx252(TimelineToolPicker, { showSelectAroundPlayhead }),
99689
- /* @__PURE__ */ jsx252("div", { "aria-hidden": "true", className: "mx-1 h-4 w-px bg-neutral-800" }),
99690
- /* @__PURE__ */ jsx252(LinkedSelectionToggle, {}),
99691
- /* @__PURE__ */ jsx252(Tooltip, { label: timelineSnapEnabled ? "Snapping on (N)" : "Snapping off (N)", children: /* @__PURE__ */ jsx252(
99912
+ return /* @__PURE__ */ jsx253("div", { className: "border-b border-neutral-800/60", children: /* @__PURE__ */ jsxs200("div", { className: "flex items-center justify-between px-2 py-0.5", children: [
99913
+ /* @__PURE__ */ jsxs200("div", { className: "flex items-center gap-0.5", children: [
99914
+ showHistory && /* @__PURE__ */ jsx253(TimelineHistoryButtons, { ...history2 }),
99915
+ /* @__PURE__ */ jsx253(TimelineToolPicker, { showSelectAroundPlayhead }),
99916
+ /* @__PURE__ */ jsx253("div", { "aria-hidden": "true", className: "mx-1 h-4 w-px bg-neutral-800" }),
99917
+ /* @__PURE__ */ jsx253(LinkedSelectionToggle, {}),
99918
+ /* @__PURE__ */ jsx253(Tooltip, { label: timelineSnapEnabled ? "Snapping on (N)" : "Snapping off (N)", children: /* @__PURE__ */ jsx253(
99692
99919
  "button",
99693
99920
  {
99694
99921
  type: "button",
@@ -99696,14 +99923,14 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99696
99923
  "aria-label": "Toggle timeline snapping",
99697
99924
  "aria-pressed": timelineSnapEnabled,
99698
99925
  className: timelineSnapEnabled ? flatActive : flatIdle,
99699
- children: /* @__PURE__ */ jsx252(Magnet, { size: 16, weight: "bold", "aria-hidden": "true" })
99926
+ children: /* @__PURE__ */ jsx253(Magnet, { size: 16, weight: "bold", "aria-hidden": "true" })
99700
99927
  }
99701
99928
  ) }),
99702
- /* @__PURE__ */ jsx252(
99929
+ /* @__PURE__ */ jsx253(
99703
99930
  Tooltip,
99704
99931
  {
99705
99932
  label: rippleEditEnabled ? "Ripple on: keeps the main track gapless" : "Ripple off: deleting a main-track clip leaves a gap",
99706
- children: /* @__PURE__ */ jsx252(
99933
+ children: /* @__PURE__ */ jsx253(
99707
99934
  "button",
99708
99935
  {
99709
99936
  type: "button",
@@ -99711,12 +99938,12 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99711
99938
  "aria-label": "Toggle ripple edit",
99712
99939
  "aria-pressed": rippleEditEnabled,
99713
99940
  className: rippleEditEnabled ? flatActive : flatIdle,
99714
- children: /* @__PURE__ */ jsx252(RippleEditIcon, { size: 16 })
99941
+ children: /* @__PURE__ */ jsx253(RippleEditIcon, { size: 16 })
99715
99942
  }
99716
99943
  )
99717
99944
  }
99718
99945
  ),
99719
- projectHasAudio && /* @__PURE__ */ jsx252(Tooltip, { label: audioMetersVisible ? "Hide audio meters" : "Show audio meters", children: /* @__PURE__ */ jsx252(
99946
+ projectHasAudio && /* @__PURE__ */ jsx253(Tooltip, { label: audioMetersVisible ? "Hide audio meters" : "Show audio meters", children: /* @__PURE__ */ jsx253(
99720
99947
  "button",
99721
99948
  {
99722
99949
  type: "button",
@@ -99724,15 +99951,15 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99724
99951
  "aria-label": "Toggle audio meters",
99725
99952
  "aria-pressed": audioMetersVisible,
99726
99953
  className: audioMetersVisible ? flatActive : flatIdle,
99727
- children: /* @__PURE__ */ jsx252(AudioMetersIcon, { size: 16 })
99954
+ children: /* @__PURE__ */ jsx253(AudioMetersIcon, { size: 16 })
99728
99955
  }
99729
99956
  ) }),
99730
- showKeyframes && /* @__PURE__ */ jsxs199(Fragment70, { children: [
99731
- /* @__PURE__ */ jsx252(
99957
+ showKeyframes && /* @__PURE__ */ jsxs200(Fragment70, { children: [
99958
+ /* @__PURE__ */ jsx253(
99732
99959
  Tooltip,
99733
99960
  {
99734
99961
  label: keyframePathEndpoint ? "Motion path endpoints cannot be removed" : !onToggleKeyframe ? "Select an animated element to add keyframes" : keyframeIsMotionPath ? keyframeWillExtend ? "Extend motion path to playhead (K)" : keyframeState === "active" ? "Remove waypoint from motion path (K)" : "Add waypoint to motion path (K)" : keyframeState === "active" ? "Remove keyframe at playhead (K)" : keyframeState === "inactive" ? keyframeWillExtend ? "Add keyframe at playhead, extends animation (K)" : "Add keyframe at playhead (K)" : "Add keyframe (K)",
99735
- children: /* @__PURE__ */ jsx252(
99962
+ children: /* @__PURE__ */ jsx253(
99736
99963
  "button",
99737
99964
  {
99738
99965
  type: "button",
@@ -99740,7 +99967,7 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99740
99967
  onClick: onToggleKeyframe,
99741
99968
  "aria-label": keyframePathEndpoint ? "Motion path endpoint" : keyframeIsMotionPath ? keyframeState === "active" ? "Remove motion path waypoint" : keyframeWillExtend ? "Extend motion path to playhead" : "Add motion path waypoint" : keyframeState === "active" ? "Remove keyframe at playhead" : "Add keyframe at playhead",
99742
99969
  className: !onToggleKeyframe ? flatDisabled : `${flatBtn} active:scale-[0.98] hover:bg-hover ${keyframeState === "active" ? "text-accent-ink" : keyframeState === "inactive" ? "text-neutral-400 hover:text-accent-ink" : "text-neutral-600 hover:text-neutral-400"}`,
99743
- children: /* @__PURE__ */ jsx252("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "currentColor", children: keyframeState === "active" ? /* @__PURE__ */ jsx252("path", { d: "M5 0.5L9.5 5L5 9.5L0.5 5Z" }) : /* @__PURE__ */ jsx252(
99970
+ children: /* @__PURE__ */ jsx253("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "currentColor", children: keyframeState === "active" ? /* @__PURE__ */ jsx253("path", { d: "M5 0.5L9.5 5L5 9.5L0.5 5Z" }) : /* @__PURE__ */ jsx253(
99744
99971
  "path",
99745
99972
  {
99746
99973
  d: "M5 1.2L8.8 5L5 8.8L1.2 5Z",
@@ -99753,11 +99980,11 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99753
99980
  )
99754
99981
  }
99755
99982
  ),
99756
- /* @__PURE__ */ jsx252(
99983
+ /* @__PURE__ */ jsx253(
99757
99984
  Tooltip,
99758
99985
  {
99759
99986
  label: autoKeyframeEnabled ? "Auto-record manual edits as keyframes (click to turn off)" : "Manual edits will not be recorded as keyframes (click to turn on)",
99760
- children: /* @__PURE__ */ jsx252(
99987
+ children: /* @__PURE__ */ jsx253(
99761
99988
  "button",
99762
99989
  {
99763
99990
  type: "button",
@@ -99765,9 +99992,9 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99765
99992
  "aria-label": "Auto-record manual edits as keyframes",
99766
99993
  "aria-pressed": autoKeyframeEnabled,
99767
99994
  className: `${flatBtn} active:scale-[0.98] hover:bg-hover ${autoKeyframeEnabled ? "text-danger-ink" : "text-neutral-600 hover:text-neutral-400"}`,
99768
- children: /* @__PURE__ */ jsxs199("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "none", children: [
99769
- /* @__PURE__ */ jsx252("path", { d: "M5 0.7L9.3 5L5 9.3L0.7 5Z", stroke: "currentColor", strokeWidth: "1" }),
99770
- /* @__PURE__ */ jsx252(
99995
+ children: /* @__PURE__ */ jsxs200("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "none", children: [
99996
+ /* @__PURE__ */ jsx253("path", { d: "M5 0.7L9.3 5L5 9.3L0.7 5Z", stroke: "currentColor", strokeWidth: "1" }),
99997
+ /* @__PURE__ */ jsx253(
99771
99998
  "circle",
99772
99999
  {
99773
100000
  cx: "5",
@@ -99788,11 +100015,11 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99788
100015
  const el = selectedElementId ? elements.find((e) => (e.key ?? e.id) === selectedElementId) : null;
99789
100016
  const splittable = el != null && canSplitElement(el);
99790
100017
  const canSplit = splittable && currentTime > el.start && currentTime < el.start + el.duration;
99791
- return /* @__PURE__ */ jsx252(
100018
+ return /* @__PURE__ */ jsx253(
99792
100019
  Tooltip,
99793
100020
  {
99794
100021
  label: canSplit ? `Split at playhead (${splitShortcut})` : splittable ? "Move the playhead inside the clip to split" : "Select a clip to split",
99795
- children: /* @__PURE__ */ jsx252(
100022
+ children: /* @__PURE__ */ jsx253(
99796
100023
  "button",
99797
100024
  {
99798
100025
  type: "button",
@@ -99802,7 +100029,7 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99802
100029
  if (canSplit && el) onSplitElement(el, currentTime);
99803
100030
  },
99804
100031
  className: canSplit ? flatIdle : flatDisabled,
99805
- children: /* @__PURE__ */ jsxs199(
100032
+ children: /* @__PURE__ */ jsxs200(
99806
100033
  "svg",
99807
100034
  {
99808
100035
  width: "16",
@@ -99815,8 +100042,8 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99815
100042
  strokeLinejoin: "round",
99816
100043
  "aria-hidden": "true",
99817
100044
  children: [
99818
- /* @__PURE__ */ jsx252("path", { d: "M5 3 L7 3 L7 13 L5 13" }),
99819
- /* @__PURE__ */ jsx252("path", { d: "M11 3 L9 3 L9 13 L11 13" })
100045
+ /* @__PURE__ */ jsx253("path", { d: "M5 3 L7 3 L7 13 L5 13" }),
100046
+ /* @__PURE__ */ jsx253("path", { d: "M11 3 L9 3 L9 13 L11 13" })
99820
100047
  ]
99821
100048
  }
99822
100049
  )
@@ -99828,11 +100055,11 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99828
100055
  (() => {
99829
100056
  if (!showAddBeat) return null;
99830
100057
  const canAdd = beatAnalysisReady && canAddBeatAt(currentTime);
99831
- return /* @__PURE__ */ jsx252(
100058
+ return /* @__PURE__ */ jsx253(
99832
100059
  Tooltip,
99833
100060
  {
99834
100061
  label: !beatAnalysisReady ? "Add a music track with beat analysis to place beats" : canAdd ? "Add beat at playhead" : "A beat already exists at the playhead",
99835
- children: /* @__PURE__ */ jsx252(
100062
+ children: /* @__PURE__ */ jsx253(
99836
100063
  "button",
99837
100064
  {
99838
100065
  type: "button",
@@ -99842,7 +100069,7 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99842
100069
  if (canAdd) addBeatAtCompositionTime(currentTime);
99843
100070
  },
99844
100071
  className: canAdd ? `${flatBtn} text-text-2 hover:bg-hover hover:text-accent-ink active:scale-[0.98]` : flatDisabled,
99845
- children: /* @__PURE__ */ jsx252("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx252(
100072
+ children: /* @__PURE__ */ jsx253("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx253(
99846
100073
  "path",
99847
100074
  {
99848
100075
  d: "M21 10C21 12.2091 16.9706 14 12 14M21 10C21 7.79086 16.9706 6 12 6C7.02944 6 3 7.79086 3 10M21 10V16C21 18.2091 16.9706 20 12 20M12 14C7.02944 14 3 12.2091 3 10M12 14V20M3 10V16C3 18.2091 7.02944 20 12 20M7 19.3264V13.3264M17 19.3264V13.3264M12 10L20 4",
@@ -99858,13 +100085,13 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99858
100085
  );
99859
100086
  })()
99860
100087
  ] }),
99861
- /* @__PURE__ */ jsxs199("div", { className: "flex items-center gap-0.5", children: [
100088
+ /* @__PURE__ */ jsxs200("div", { className: "flex items-center gap-0.5", children: [
99862
100089
  rightActions,
99863
- /* @__PURE__ */ jsx252(
100090
+ /* @__PURE__ */ jsx253(
99864
100091
  Tooltip,
99865
100092
  {
99866
100093
  label: thumbnailsVisible ? "Hide thumbnails: labels only" : "Show thumbnails: posters stay visible; richer previews appear on interaction",
99867
- children: /* @__PURE__ */ jsx252(
100094
+ children: /* @__PURE__ */ jsx253(
99868
100095
  "button",
99869
100096
  {
99870
100097
  type: "button",
@@ -99872,12 +100099,12 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99872
100099
  "aria-pressed": thumbnailsVisible,
99873
100100
  onClick: () => setThumbnailMode(thumbnailsVisible ? "hidden" : "adaptive"),
99874
100101
  className: `h-7 px-2 rounded-md text-[11px] font-medium transition-colors ${thumbnailsVisible ? "bg-studio-accent/10 text-accent-ink" : "text-neutral-400 hover:bg-hover hover:text-neutral-200"}`,
99875
- children: /* @__PURE__ */ jsx252(Image3, { size: 16, "aria-hidden": "true" })
100102
+ children: /* @__PURE__ */ jsx253(Image3, { size: 16, "aria-hidden": "true" })
99876
100103
  }
99877
100104
  )
99878
100105
  }
99879
100106
  ),
99880
- /* @__PURE__ */ jsx252(Tooltip, { label: "Fit timeline to width", children: /* @__PURE__ */ jsx252(
100107
+ /* @__PURE__ */ jsx253(Tooltip, { label: "Fit timeline to width", children: /* @__PURE__ */ jsx253(
99881
100108
  "button",
99882
100109
  {
99883
100110
  type: "button",
@@ -99885,20 +100112,20 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99885
100112
  "aria-label": "Fit timeline to width",
99886
100113
  "aria-pressed": zoomMode === "fit",
99887
100114
  className: `h-7 px-2 rounded-md text-[11px] font-medium transition-colors ${zoomMode === "fit" ? "bg-studio-accent/10 text-accent-ink" : "text-neutral-400 hover:bg-hover hover:text-neutral-200"}`,
99888
- children: /* @__PURE__ */ jsx252(ArrowsOutLineHorizontal, { size: 16, "aria-hidden": "true" })
100115
+ children: /* @__PURE__ */ jsx253(ArrowsOutLineHorizontal, { size: 16, "aria-hidden": "true" })
99889
100116
  }
99890
100117
  ) }),
99891
- /* @__PURE__ */ jsx252(Tooltip, { label: "Zoom out", children: /* @__PURE__ */ jsx252(
100118
+ /* @__PURE__ */ jsx253(Tooltip, { label: "Zoom out", children: /* @__PURE__ */ jsx253(
99892
100119
  "button",
99893
100120
  {
99894
100121
  type: "button",
99895
100122
  "aria-label": "Zoom out",
99896
100123
  onClick: () => zoomTimelineStep("out"),
99897
100124
  className: flatIdle,
99898
- children: /* @__PURE__ */ jsx252(MagnifyingGlassMinus, { size: 16, "aria-hidden": "true" })
100125
+ children: /* @__PURE__ */ jsx253(MagnifyingGlassMinus, { size: 16, "aria-hidden": "true" })
99899
100126
  }
99900
100127
  ) }),
99901
- /* @__PURE__ */ jsx252(
100128
+ /* @__PURE__ */ jsx253(
99902
100129
  "input",
99903
100130
  {
99904
100131
  type: "range",
@@ -99913,17 +100140,17 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99913
100140
  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"
99914
100141
  }
99915
100142
  ),
99916
- /* @__PURE__ */ jsx252(Tooltip, { label: "Zoom in", children: /* @__PURE__ */ jsx252(
100143
+ /* @__PURE__ */ jsx253(Tooltip, { label: "Zoom in", children: /* @__PURE__ */ jsx253(
99917
100144
  "button",
99918
100145
  {
99919
100146
  type: "button",
99920
100147
  "aria-label": "Zoom in",
99921
100148
  onClick: () => zoomTimelineStep("in"),
99922
100149
  className: flatIdle,
99923
- children: /* @__PURE__ */ jsx252(MagnifyingGlassPlus, { size: 16, "aria-hidden": "true" })
100150
+ children: /* @__PURE__ */ jsx253(MagnifyingGlassPlus, { size: 16, "aria-hidden": "true" })
99924
100151
  }
99925
100152
  ) }),
99926
- /* @__PURE__ */ jsx252(
100153
+ /* @__PURE__ */ jsx253(
99927
100154
  "span",
99928
100155
  {
99929
100156
  className: "ml-1 w-[38px] text-right font-mono text-[11px] tabular-nums text-neutral-500 select-none",
@@ -99936,23 +100163,23 @@ var TimelineToolbar = memo62(function TimelineToolbar2({
99936
100163
  });
99937
100164
 
99938
100165
  // src/components/StudioSplash.tsx
99939
- import { jsx as jsx253, jsxs as jsxs200 } from "react/jsx-runtime";
100166
+ import { jsx as jsx254, jsxs as jsxs201 } from "react/jsx-runtime";
99940
100167
  function StudioSplash({ waiting }) {
99941
- return /* @__PURE__ */ jsx253("div", { className: "h-full w-full bg-neutral-950 flex items-center justify-center", children: waiting ? /* @__PURE__ */ jsxs200("div", { className: "flex flex-col items-center gap-3 text-center px-6", role: "status", children: [
99942
- /* @__PURE__ */ jsx253("div", { className: "w-4 h-4 rounded-full border-2 border-neutral-700 border-t-neutral-500 animate-spin motion-reduce:animate-none" }),
99943
- /* @__PURE__ */ jsxs200("p", { className: "text-xs text-neutral-600", children: [
100168
+ return /* @__PURE__ */ jsx254("div", { className: "h-full w-full bg-neutral-950 flex items-center justify-center", children: waiting ? /* @__PURE__ */ jsxs201("div", { className: "flex flex-col items-center gap-3 text-center px-6", role: "status", children: [
100169
+ /* @__PURE__ */ jsx254("div", { className: "w-4 h-4 rounded-full border-2 border-neutral-700 border-t-neutral-500 animate-spin motion-reduce:animate-none" }),
100170
+ /* @__PURE__ */ jsxs201("p", { className: "text-xs text-neutral-600", children: [
99944
100171
  "Waiting for preview server\u2026 run",
99945
100172
  " ",
99946
- /* @__PURE__ */ jsx253("code", { className: "text-neutral-500 font-mono", children: "npm run dev" })
100173
+ /* @__PURE__ */ jsx254("code", { className: "text-neutral-500 font-mono", children: "npm run dev" })
99947
100174
  ] })
99948
- ] }) : /* @__PURE__ */ jsxs200("div", { className: "flex flex-col items-center gap-3 text-center px-6", role: "status", children: [
99949
- /* @__PURE__ */ jsx253("div", { className: "w-4 h-4 rounded-full bg-studio-accent animate-pulse motion-reduce:animate-none" }),
99950
- /* @__PURE__ */ jsx253("p", { className: "text-xs text-neutral-600", children: "Connecting to project\u2026" })
100175
+ ] }) : /* @__PURE__ */ jsxs201("div", { className: "flex flex-col items-center gap-3 text-center px-6", role: "status", children: [
100176
+ /* @__PURE__ */ jsx254("div", { className: "w-4 h-4 rounded-full bg-studio-accent animate-pulse motion-reduce:animate-none" }),
100177
+ /* @__PURE__ */ jsx254("p", { className: "text-xs text-neutral-600", children: "Connecting to project\u2026" })
99951
100178
  ] }) });
99952
100179
  }
99953
100180
 
99954
100181
  // src/hooks/useServerConnection.ts
99955
- import { useEffect as useEffect157, useState as useState162 } from "react";
100182
+ import { useEffect as useEffect158, useState as useState163 } from "react";
99956
100183
  async function resolveHashProject(id) {
99957
100184
  try {
99958
100185
  const res = await studioApiFetch(`/api/projects/${encodeURIComponent(id)}`);
@@ -99982,9 +100209,9 @@ function reportHashVerdict(stage, verdict) {
99982
100209
  });
99983
100210
  }
99984
100211
  function useServerConnection() {
99985
- const [projectId, setProjectId] = useState162(null);
99986
- const [resolving, setResolving] = useState162(true);
99987
- const [waitingForServer, setWaitingForServer] = useState162(false);
100212
+ const [projectId, setProjectId] = useState163(null);
100213
+ const [resolving, setResolving] = useState163(true);
100214
+ const [waitingForServer, setWaitingForServer] = useState163(false);
99988
100215
  useMountEffect(() => {
99989
100216
  const hashProjectId = parseProjectIdFromHash(window.location.hash);
99990
100217
  let cancelled = false;
@@ -100026,7 +100253,7 @@ function useServerConnection() {
100026
100253
  if (retryTimer !== null) clearTimeout(retryTimer);
100027
100254
  };
100028
100255
  });
100029
- useEffect157(() => {
100256
+ useEffect158(() => {
100030
100257
  const onHashChange = () => {
100031
100258
  const next = parseProjectIdFromHash(window.location.hash);
100032
100259
  if (!next || next === projectId) return;
@@ -100052,9 +100279,9 @@ function useServerConnection() {
100052
100279
  }
100053
100280
 
100054
100281
  // src/hooks/useStudioSessionStart.ts
100055
- import { useEffect as useEffect158 } from "react";
100282
+ import { useEffect as useEffect159 } from "react";
100056
100283
  function useStudioSessionStart(projectId, resolving, waitingForServer) {
100057
- useEffect158(() => {
100284
+ useEffect159(() => {
100058
100285
  if (resolving || waitingForServer || hasFiredSessionStart()) return;
100059
100286
  markSessionStartFired();
100060
100287
  trackStudioSessionStart({ has_project: projectId != null });
@@ -100062,13 +100289,13 @@ function useStudioSessionStart(projectId, resolving, waitingForServer) {
100062
100289
  }
100063
100290
 
100064
100291
  // src/hooks/useTimelineAddAtPlayhead.ts
100065
- import { useCallback as useCallback192 } from "react";
100292
+ import { useCallback as useCallback193 } from "react";
100066
100293
  function useTimelineAddAtPlayhead(addAsset, addComposition, projectId) {
100067
100294
  const placement = () => ({ start: usePlayerStore.getState().currentTime, track: 0 });
100068
100295
  return useStableHandlers(
100069
100296
  {
100070
- addAssetAtPlayhead: useCallback192((path) => addAsset(path, placement()), [addAsset]),
100071
- addCompositionAtPlayhead: useCallback192(
100297
+ addAssetAtPlayhead: useCallback193((path) => addAsset(path, placement()), [addAsset]),
100298
+ addCompositionAtPlayhead: useCallback193(
100072
100299
  (path) => addComposition(path, placement()),
100073
100300
  [addComposition]
100074
100301
  )
@@ -100078,10 +100305,10 @@ function useTimelineAddAtPlayhead(addAsset, addComposition, projectId) {
100078
100305
  }
100079
100306
 
100080
100307
  // src/hooks/useActiveComposition.ts
100081
- import { useState as useState163 } from "react";
100308
+ import { useState as useState164 } from "react";
100082
100309
 
100083
100310
  // src/hooks/useHydrateActiveCompPathFromUrl.ts
100084
- import { useEffect as useEffect159 } from "react";
100311
+ import { useEffect as useEffect160 } from "react";
100085
100312
  function useHydrateActiveCompPathFromUrl({
100086
100313
  hydrated,
100087
100314
  fileTreeLoaded,
@@ -100090,7 +100317,7 @@ function useHydrateActiveCompPathFromUrl({
100090
100317
  setActiveCompPath,
100091
100318
  setHydrated
100092
100319
  }) {
100093
- useEffect159(() => {
100320
+ useEffect160(() => {
100094
100321
  if (hydrated) return;
100095
100322
  if (!fileTreeLoaded) return;
100096
100323
  const nextCompPath = normalizeStudioCompositionPath(
@@ -100103,7 +100330,7 @@ function useHydrateActiveCompPathFromUrl({
100103
100330
  }
100104
100331
 
100105
100332
  // src/hooks/useAutoOpenRootComposition.ts
100106
- import { useEffect as useEffect160, useRef as useRef187 } from "react";
100333
+ import { useEffect as useEffect161, useRef as useRef188 } from "react";
100107
100334
  function useAutoOpenRootComposition({
100108
100335
  projectId,
100109
100336
  activeCompPath,
@@ -100111,8 +100338,8 @@ function useAutoOpenRootComposition({
100111
100338
  masterCompPath,
100112
100339
  onSelectComposition
100113
100340
  }) {
100114
- const settledForProjectRef = useRef187(void 0);
100115
- useEffect160(() => {
100341
+ const settledForProjectRef = useRef188(void 0);
100342
+ useEffect161(() => {
100116
100343
  if (settledForProjectRef.current === projectId) return;
100117
100344
  if (!activeCompPathHydrated) return;
100118
100345
  if (activeCompPath !== null) {
@@ -100126,14 +100353,14 @@ function useAutoOpenRootComposition({
100126
100353
  }
100127
100354
 
100128
100355
  // src/hooks/useCompositionContentLoader.ts
100129
- import { useCallback as useCallback193 } from "react";
100356
+ import { useCallback as useCallback194 } from "react";
100130
100357
  function useCompositionContentLoader({
100131
100358
  projectId,
100132
100359
  setEditingFile,
100133
100360
  setActiveCompPath,
100134
100361
  showToast
100135
100362
  }) {
100136
- return useCallback193(
100363
+ return useCallback194(
100137
100364
  (comp) => {
100138
100365
  if (!projectId) return;
100139
100366
  setActiveCompPath(comp.endsWith(".html") ? comp : null);
@@ -100153,14 +100380,14 @@ function useCompositionContentLoader({
100153
100380
  }
100154
100381
 
100155
100382
  // src/hooks/useResetSelectionOnProjectSwitch.ts
100156
- import { useRef as useRef188 } from "react";
100383
+ import { useRef as useRef189 } from "react";
100157
100384
  function useResetSelectionOnProjectSwitch({
100158
100385
  projectId,
100159
100386
  initialUrlStateRef,
100160
100387
  setActiveCompPath,
100161
100388
  setActiveCompPathHydrated
100162
100389
  }) {
100163
- const previousProjectIdRef = useRef188(projectId);
100390
+ const previousProjectIdRef = useRef189(projectId);
100164
100391
  if (previousProjectIdRef.current !== projectId) {
100165
100392
  const isSwitch = previousProjectIdRef.current !== null;
100166
100393
  previousProjectIdRef.current = projectId;
@@ -100182,8 +100409,8 @@ function useActiveComposition({
100182
100409
  setEditingFile,
100183
100410
  showToast
100184
100411
  }) {
100185
- const [activeCompPath, setActiveCompPath] = useState163(null);
100186
- const [activeCompPathHydrated, setActiveCompPathHydrated] = useState163(
100412
+ const [activeCompPath, setActiveCompPath] = useState164(null);
100413
+ const [activeCompPathHydrated, setActiveCompPathHydrated] = useState164(
100187
100414
  () => isHydratedFromUrlState(initialUrlStateRef.current)
100188
100415
  );
100189
100416
  useResetSelectionOnProjectSwitch({
@@ -100217,19 +100444,19 @@ function useActiveComposition({
100217
100444
  }
100218
100445
 
100219
100446
  // src/App.tsx
100220
- import { jsx as jsx254, jsxs as jsxs201 } from "react/jsx-runtime";
100447
+ import { jsx as jsx255, jsxs as jsxs202 } from "react/jsx-runtime";
100221
100448
  var getTimelineSelectionSet = () => usePlayerStore.getState().selectedElementIds;
100222
100449
  function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100223
100450
  const { projectId, resolving, waitingForServer } = useServerConnection();
100224
- const initialUrlStateRef = useRef189(readStudioUrlStateFromWindow());
100451
+ const initialUrlStateRef = useRef190(readStudioUrlStateFromWindow());
100225
100452
  useStudioSessionStart(projectId, resolving, waitingForServer);
100226
- const [compIdToSrc, setCompIdToSrc] = useState164(/* @__PURE__ */ new Map());
100453
+ const [compIdToSrc, setCompIdToSrc] = useState165(/* @__PURE__ */ new Map());
100227
100454
  const previewIframe = useOwnPreviewIframe();
100228
- const [compositionLoading, setCompositionLoading] = useState164(true);
100229
- const [refreshKey, setRefreshKeyState] = useState164(0);
100455
+ const [compositionLoading, setCompositionLoading] = useState165(true);
100456
+ const [refreshKey, setRefreshKeyState] = useState165(0);
100230
100457
  const [previewDocumentVersion, refreshPreviewDocumentVersion] = usePreviewDocumentVersion();
100231
- const [blockPreview, setBlockPreview] = useState164(null);
100232
- const previewIframeRef = useRef189(null);
100458
+ const [blockPreview, setBlockPreview] = useState165(null);
100459
+ const previewIframeRef = useRef190(null);
100233
100460
  const captionEditMode = useCaptionStore((s) => s.isEditMode);
100234
100461
  const captionHasSelection = useCaptionStore((s) => s.selectedSegmentIds.size > 0);
100235
100462
  const captionSync = useCaptionSync(projectId);
@@ -100248,14 +100475,14 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100248
100475
  rightPanelTab: initialUrlStateRef.current.rightPanelTab
100249
100476
  });
100250
100477
  const editHistory = useStableHandlers(usePersistentEditHistory({ projectId }), projectId);
100251
- const handleDomZIndexReorderCommitRef = useRef189(null);
100252
- const pendingTimelineEditPathRef = useRef189(/* @__PURE__ */ new Set());
100253
- const isGestureRecordingRef = useRef189(false);
100254
- const setRefreshKey = useCallback194((next) => {
100478
+ const handleDomZIndexReorderCommitRef = useRef190(null);
100479
+ const pendingTimelineEditPathRef = useRef190(/* @__PURE__ */ new Set());
100480
+ const isGestureRecordingRef = useRef190(false);
100481
+ const setRefreshKey = useCallback195((next) => {
100255
100482
  requestPreviewReload();
100256
100483
  setRefreshKeyState(next);
100257
100484
  }, []);
100258
- const reloadPreview = useCallback194(() => setRefreshKey((k) => k + 1), [setRefreshKey]);
100485
+ const reloadPreview = useCallback195(() => setRefreshKey((k) => k + 1), [setRefreshKey]);
100259
100486
  const fileManagerResult = useFileManager({
100260
100487
  projectId,
100261
100488
  showToast,
@@ -100263,7 +100490,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100263
100490
  setRefreshKey
100264
100491
  });
100265
100492
  const fileManager = useStableHandlers(fileManagerResult, projectId);
100266
- const masterCompPath = useMemo89(
100493
+ const masterCompPath = useMemo90(
100267
100494
  () => resolveMasterCompositionPath(fileManager.compositions),
100268
100495
  [fileManager.compositions]
100269
100496
  );
@@ -100284,7 +100511,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100284
100511
  fileManager.fileTreeLoaded,
100285
100512
  fileManager.refreshFileTree
100286
100513
  );
100287
- const activeCompPathRef = useRef189(activeCompPath);
100514
+ const activeCompPathRef = useRef190(activeCompPath);
100288
100515
  activeCompPathRef.current = activeCompPath;
100289
100516
  const renderQueue = useStableHandlers(useRenderQueue(projectId, activeCompPathRef), projectId);
100290
100517
  const previewPersistenceResult = usePreviewPersistence({
@@ -100307,9 +100534,9 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100307
100534
  reloadPreview,
100308
100535
  onOutsideChange: editHistory.noteOutsideChange
100309
100536
  });
100310
- const invalidateGsapCacheRef = useRef189(() => {
100537
+ const invalidateGsapCacheRef = useRef190(() => {
100311
100538
  });
100312
- const invalidateGsapCache = useCallback194(() => invalidateGsapCacheRef.current(), []);
100539
+ const invalidateGsapCache = useCallback195(() => invalidateGsapCacheRef.current(), []);
100313
100540
  const timelineEditing = useTimelineEditing({
100314
100541
  projectId,
100315
100542
  activeCompPath,
@@ -100365,20 +100592,20 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100365
100592
  handleAddMediaOverlay,
100366
100593
  handlePreviewBlockDrop
100367
100594
  } = useStableHandlers(blockHandlersResult, projectId);
100368
- const dismissBlockParams = useCallback194(() => setActiveBlockParams(null), [setActiveBlockParams]);
100595
+ const dismissBlockParams = useCallback195(() => setActiveBlockParams(null), [setActiveBlockParams]);
100369
100596
  const setRightPanelTab = useDismissingTabSetter(panelLayout.setRightPanelTab, dismissBlockParams);
100370
- const layout = useMemo89(
100597
+ const layout = useMemo90(
100371
100598
  () => ({ ...panelLayout, setRightPanelTab }),
100372
100599
  [panelLayout, setRightPanelTab]
100373
100600
  );
100374
- const clearDomSelectionRef = useRef189(() => {
100601
+ const clearDomSelectionRef = useRef190(() => {
100375
100602
  });
100376
- const domEditSelectionBridgeRef = useRef189(null);
100377
- const handleDomEditElementDeleteRef = useRef189(async () => {
100603
+ const domEditSelectionBridgeRef = useRef190(null);
100604
+ const handleDomEditElementDeleteRef = useRef190(async () => {
100378
100605
  });
100379
100606
  const domEditDeleteBridge = (s, o) => handleDomEditElementDeleteRef.current(s, o);
100380
- const resetKeyframesRef = useRef189(() => false);
100381
- const deleteSelectedKeyframesRef = useRef189(() => {
100607
+ const resetKeyframesRef = useRef190(() => false);
100608
+ const deleteSelectedKeyframesRef = useRef190(() => {
100382
100609
  });
100383
100610
  const { handleCopy: handleCopy2, handlePaste: handlePaste2, handleCut, handleDuplicate, canPaste } = useClipboard({
100384
100611
  projectId,
@@ -100508,9 +100735,9 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100508
100735
  const frameCapture = useStableHandlers(frameCaptureResult, projectId);
100509
100736
  const { consoleErrors, setConsoleErrors } = useConsoleErrorCapture(previewIframe);
100510
100737
  const fileDrop = useGlobalFileDrop(timelineEditing.handleTimelineFileDrop);
100511
- const handleToggleRecordingRef = useRef189(() => {
100738
+ const handleToggleRecordingRef = useRef190(() => {
100512
100739
  });
100513
- const domEditSessionRef = useRef189(domEditSession);
100740
+ const domEditSessionRef = useRef190(domEditSession);
100514
100741
  domEditSessionRef.current = domEditSession;
100515
100742
  const { gestureState, gestureRecording, handleToggleRecording } = useGestureCommit({
100516
100743
  projectId,
@@ -100526,7 +100753,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100526
100753
  previewIframe,
100527
100754
  compositionDimensions
100528
100755
  );
100529
- const handlePreviewIframeRef = useCallback194(
100756
+ const handlePreviewIframeRef = useCallback195(
100530
100757
  (iframe) => {
100531
100758
  previewIframeRef.current = iframe;
100532
100759
  usePreviewIframeStore.getState().setIframe(iframe);
@@ -100587,8 +100814,8 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100587
100814
  handlePreviewIframeRef,
100588
100815
  refreshPreviewDocumentVersion
100589
100816
  });
100590
- const timelineToolbar = useMemo89(
100591
- () => /* @__PURE__ */ jsx254(
100817
+ const timelineToolbar = useMemo90(
100818
+ () => /* @__PURE__ */ jsx255(
100592
100819
  TimelineToolbar,
100593
100820
  {
100594
100821
  domEditSession: toolbarSession,
@@ -100597,10 +100824,10 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100597
100824
  ),
100598
100825
  [toolbarSession, timelineEditing.handleTimelineElementSplit]
100599
100826
  );
100600
- const handleExport = useCallback194(() => {
100827
+ const handleExport = useCallback195(() => {
100601
100828
  void previewPersistence.waitForPendingDomEditSaves().then(() => renderQueue.startRender(void 0));
100602
100829
  }, [previewPersistence, renderQueue]);
100603
- const clearConsoleErrors = useCallback194(() => setConsoleErrors(null), [setConsoleErrors]);
100830
+ const clearConsoleErrors = useCallback195(() => setConsoleErrors(null), [setConsoleErrors]);
100604
100831
  const panels = useStudioSidePanels(
100605
100832
  {
100606
100833
  onSelectComposition: handleSelectComposition,
@@ -100636,15 +100863,15 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100636
100863
  projectId
100637
100864
  );
100638
100865
  if (resolving || waitingForServer || !projectId)
100639
- return /* @__PURE__ */ jsx254(StudioSplash, { waiting: waitingForServer });
100640
- return /* @__PURE__ */ jsx254(StudioShellProvider, { value: studioCtxValue, children: /* @__PURE__ */ jsx254(StudioPlaybackProvider, { value: studioCtxValue, children: /* @__PURE__ */ jsx254(PanelLayoutProvider, { value: layout, children: /* @__PURE__ */ jsx254(FileManagerProvider, { value: fileManager, children: /* @__PURE__ */ jsx254(DomEditProvider, { value: domEditSession, children: /* @__PURE__ */ jsxs201(
100866
+ return /* @__PURE__ */ jsx255(StudioSplash, { waiting: waitingForServer });
100867
+ return /* @__PURE__ */ jsx255(StudioShellProvider, { value: studioCtxValue, children: /* @__PURE__ */ jsx255(StudioPlaybackProvider, { value: studioCtxValue, children: /* @__PURE__ */ jsx255(PanelLayoutProvider, { value: layout, children: /* @__PURE__ */ jsx255(FileManagerProvider, { value: fileManager, children: /* @__PURE__ */ jsx255(DomEditProvider, { value: domEditSession, children: /* @__PURE__ */ jsxs202(
100641
100868
  "div",
100642
100869
  {
100643
100870
  className: "flex flex-col h-full w-full bg-neutral-950 relative",
100644
100871
  onDragOver: fileDrop.onDragOver,
100645
100872
  onDrop: fileDrop.onDrop,
100646
100873
  children: [
100647
- /* @__PURE__ */ jsx254(
100874
+ /* @__PURE__ */ jsx255(
100648
100875
  StudioHeader,
100649
100876
  {
100650
100877
  captureFrameHref: frameCapture.captureFrameHref,
@@ -100657,17 +100884,17 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100657
100884
  onExport: handleExport
100658
100885
  }
100659
100886
  ),
100660
- previewPersistence.domEditSaveQueuePaused && !externalFileChanges.blocked && /* @__PURE__ */ jsx254(
100887
+ previewPersistence.domEditSaveQueuePaused && !externalFileChanges.blocked && /* @__PURE__ */ jsx255(
100661
100888
  SaveQueuePausedBanner,
100662
100889
  {
100663
100890
  message: previewPersistence.domEditSaveQueuePaused,
100664
100891
  onRetry: previewPersistence.resetDomEditSaveQueueBreaker
100665
100892
  }
100666
100893
  ),
100667
- /* @__PURE__ */ jsx254(ExternalFileConflictBanner, { coordinator: externalFileChanges }),
100668
- sdkHandle.unreachableProject && /* @__PURE__ */ jsx254(ProjectUnreachableBanner, { projectId: sdkHandle.unreachableProject }),
100669
- sdkHandle.compositionMissing && activeCompPath && /* @__PURE__ */ jsx254(CompositionMissingBanner, { path: activeCompPath }),
100670
- /* @__PURE__ */ jsx254(
100894
+ /* @__PURE__ */ jsx255(ExternalFileConflictBanner, { coordinator: externalFileChanges }),
100895
+ sdkHandle.unreachableProject && /* @__PURE__ */ jsx255(ProjectUnreachableBanner, { projectId: sdkHandle.unreachableProject }),
100896
+ sdkHandle.compositionMissing && activeCompPath && /* @__PURE__ */ jsx255(CompositionMissingBanner, { path: activeCompPath }),
100897
+ /* @__PURE__ */ jsx255(
100671
100898
  EditorShell,
100672
100899
  {
100673
100900
  readOnlyPreview,
@@ -100711,7 +100938,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100711
100938
  gestureOverlay
100712
100939
  }
100713
100940
  ),
100714
- /* @__PURE__ */ jsx254(
100941
+ /* @__PURE__ */ jsx255(
100715
100942
  StudioOverlays,
100716
100943
  {
100717
100944
  projectId,
@@ -100732,35 +100959,35 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100732
100959
  }
100733
100960
 
100734
100961
  // src/hooks/useElementPicker.ts
100735
- import { useState as useState165, useCallback as useCallback195, useRef as useRef190 } from "react";
100962
+ import { useState as useState166, useCallback as useCallback196, useRef as useRef191 } from "react";
100736
100963
  function useElementPicker(iframeRef, options) {
100737
- const [isPickMode, setIsPickMode] = useState165(false);
100738
- const [pickedElement, setPickedElement] = useState165(null);
100739
- const activeOverrideRef = useRef190(null);
100740
- const getActiveIframe = useCallback195(() => {
100964
+ const [isPickMode, setIsPickMode] = useState166(false);
100965
+ const [pickedElement, setPickedElement] = useState166(null);
100966
+ const activeOverrideRef = useRef191(null);
100967
+ const getActiveIframe = useCallback196(() => {
100741
100968
  return activeOverrideRef.current ?? iframeRef.current;
100742
100969
  }, [iframeRef]);
100743
- const setActiveIframe = useCallback195((el) => {
100970
+ const setActiveIframe = useCallback196((el) => {
100744
100971
  activeOverrideRef.current = el;
100745
100972
  }, []);
100746
- const enablePick = useCallback195(() => {
100973
+ const enablePick = useCallback196(() => {
100747
100974
  try {
100748
100975
  postRuntimeControlMessage(getActiveIframe()?.contentWindow, "enable-pick-mode");
100749
100976
  setIsPickMode(true);
100750
100977
  } catch {
100751
100978
  }
100752
100979
  }, [getActiveIframe]);
100753
- const disablePick = useCallback195(() => {
100980
+ const disablePick = useCallback196(() => {
100754
100981
  try {
100755
100982
  postRuntimeControlMessage(getActiveIframe()?.contentWindow, "disable-pick-mode");
100756
100983
  } catch {
100757
100984
  }
100758
100985
  setIsPickMode(false);
100759
100986
  }, [getActiveIframe]);
100760
- const clearPick = useCallback195(() => {
100987
+ const clearPick = useCallback196(() => {
100761
100988
  setPickedElement(null);
100762
100989
  }, []);
100763
- const windowsThatWerePrimary = useRef190(/* @__PURE__ */ new WeakSet());
100990
+ const windowsThatWerePrimary = useRef191(/* @__PURE__ */ new WeakSet());
100764
100991
  useMountEffect(() => {
100765
100992
  const handleMessage = (e) => {
100766
100993
  const data = e.data;
@@ -100787,11 +101014,11 @@ function useElementPicker(iframeRef, options) {
100787
101014
  window.addEventListener("message", handleMessage);
100788
101015
  return () => window.removeEventListener("message", handleMessage);
100789
101016
  });
100790
- const optionsRef = useRef190(options);
101017
+ const optionsRef = useRef191(options);
100791
101018
  optionsRef.current = options;
100792
- const pendingWritesRef = useRef190(/* @__PURE__ */ new Map());
101019
+ const pendingWritesRef = useRef191(/* @__PURE__ */ new Map());
100793
101020
  settlePendingWrites(pendingWritesRef.current, options?.workspaceFiles);
100794
- const syncToSource = useCallback195(
101021
+ const syncToSource = useCallback196(
100795
101022
  (picked, live, iframe, op) => {
100796
101023
  const opts = optionsRef.current;
100797
101024
  if (!opts?.workspaceFiles || !opts.onSyncFiles) return;
@@ -100805,7 +101032,7 @@ function useElementPicker(iframeRef, options) {
100805
101032
  },
100806
101033
  []
100807
101034
  );
100808
- const setStyle = useCallback195(
101035
+ const setStyle = useCallback196(
100809
101036
  (prop, value) => {
100810
101037
  const activeIframe = getActiveIframe();
100811
101038
  if (!pickedElement?.selector || !activeIframe) return;
@@ -100831,7 +101058,7 @@ function useElementPicker(iframeRef, options) {
100831
101058
  },
100832
101059
  [pickedElement, getActiveIframe, syncToSource]
100833
101060
  );
100834
- const setDataAttr = useCallback195(
101061
+ const setDataAttr = useCallback196(
100835
101062
  (attr, value) => {
100836
101063
  const activeIframe = getActiveIframe();
100837
101064
  if (!pickedElement?.selector || !activeIframe) return;
@@ -100857,7 +101084,7 @@ function useElementPicker(iframeRef, options) {
100857
101084
  },
100858
101085
  [pickedElement, getActiveIframe, syncToSource]
100859
101086
  );
100860
- const setTextContent = useCallback195(
101087
+ const setTextContent = useCallback196(
100861
101088
  (text) => {
100862
101089
  const activeIframe = getActiveIframe();
100863
101090
  if (!pickedElement?.selector || !activeIframe) return;
@@ -100878,7 +101105,7 @@ function useElementPicker(iframeRef, options) {
100878
101105
  },
100879
101106
  [pickedElement, getActiveIframe, syncToSource]
100880
101107
  );
100881
- const activeIframeRef = useRef190(null);
101108
+ const activeIframeRef = useRef191(null);
100882
101109
  activeIframeRef.current = getActiveIframe();
100883
101110
  return {
100884
101111
  isPickMode,
@@ -101096,7 +101323,7 @@ function findElementBlock(html2, elementId2) {
101096
101323
  }
101097
101324
 
101098
101325
  // src/hooks/useDomStyleCommit.ts
101099
- import { useCallback as useCallback196, useMemo as useMemo90, useRef as useRef191, useState as useState166 } from "react";
101326
+ import { useCallback as useCallback197, useMemo as useMemo91, useRef as useRef192, useState as useState167 } from "react";
101100
101327
  var noop = () => {
101101
101328
  };
101102
101329
  function useDomStyleCommit({
@@ -101107,12 +101334,12 @@ function useDomStyleCommit({
101107
101334
  activeCompPath = null,
101108
101335
  showToast = noop
101109
101336
  }) {
101110
- const projectIdRef = useRef191(projectId);
101337
+ const projectIdRef = useRef192(projectId);
101111
101338
  projectIdRef.current = projectId;
101112
- const [queue2] = useState166(createDomEditSaveQueue);
101339
+ const [queue2] = useState167(createDomEditSaveQueue);
101113
101340
  useMountEffect(() => () => queue2.destroy());
101114
- const versions = useRef191(/* @__PURE__ */ new Map()).current;
101115
- const editHistory = useMemo90(() => ({ recordEdit }), [recordEdit]);
101341
+ const versions = useRef192(/* @__PURE__ */ new Map()).current;
101342
+ const editHistory = useMemo91(() => ({ recordEdit }), [recordEdit]);
101116
101343
  const persistDomEditOperations = useDomEditPersist({
101117
101344
  activeCompPath,
101118
101345
  previewIframeRef: iframeRef,
@@ -101123,7 +101350,7 @@ function useDomStyleCommit({
101123
101350
  projectIdRef,
101124
101351
  reloadPreview: noop
101125
101352
  });
101126
- const commitStyle = useCallback196(
101353
+ const commitStyle = useCallback197(
101127
101354
  (selection, styles) => {
101128
101355
  queue2.reset();
101129
101356
  return projectIdRef.current ? commitDomStyles(
@@ -101144,7 +101371,7 @@ function useDomStyleCommit({
101144
101371
  }
101145
101372
 
101146
101373
  // src/hooks/useDomGeometryCommit.ts
101147
- import { useCallback as useCallback197, useMemo as useMemo91, useRef as useRef192, useState as useState167 } from "react";
101374
+ import { useCallback as useCallback198, useMemo as useMemo92, useRef as useRef193, useState as useState168 } from "react";
101148
101375
  var noop2 = () => {
101149
101376
  };
101150
101377
  var NO_SELECTED_ANIMATIONS = [];
@@ -101157,10 +101384,10 @@ function useDomGeometryCommit({
101157
101384
  showToast = noop2,
101158
101385
  reloadPreview
101159
101386
  }) {
101160
- const projectIdRef = useRef192(projectId);
101387
+ const projectIdRef = useRef193(projectId);
101161
101388
  projectIdRef.current = projectId;
101162
- const pending2 = useRef192(/* @__PURE__ */ new Set()).current;
101163
- const editHistory = useMemo91(() => ({ recordEdit }), [recordEdit]);
101389
+ const pending2 = useRef193(/* @__PURE__ */ new Set()).current;
101390
+ const editHistory = useMemo92(() => ({ recordEdit }), [recordEdit]);
101164
101391
  const { bump: bumpGsapCache } = useGsapCacheVersion();
101165
101392
  const gsap3 = useGsapScriptCommits({
101166
101393
  projectIdRef,
@@ -101172,7 +101399,7 @@ function useDomGeometryCommit({
101172
101399
  showToast,
101173
101400
  writeProjectFile
101174
101401
  });
101175
- const [queue2] = useState167(createDomEditSaveQueue);
101402
+ const [queue2] = useState168(createDomEditSaveQueue);
101176
101403
  useMountEffect(() => () => queue2.destroy());
101177
101404
  const persistDomEditOperations = useDomEditPersist({
101178
101405
  activeCompPath,
@@ -101189,7 +101416,7 @@ function useDomGeometryCommit({
101189
101416
  persistDomEditOperations,
101190
101417
  showToast
101191
101418
  });
101192
- const commitWithFreshQueue = useCallback197(
101419
+ const commitWithFreshQueue = useCallback198(
101193
101420
  (...args) => {
101194
101421
  queue2.reset();
101195
101422
  return commitPositionPatchToHtml(...args);
@@ -101228,7 +101455,7 @@ function useDomGeometryCommit({
101228
101455
  setArcPath: gsap3.setArcPath,
101229
101456
  updateArcSegment: gsap3.updateArcSegment
101230
101457
  });
101231
- const saved2 = useCallback197(
101458
+ const saved2 = useCallback198(
101232
101459
  async (commit2, restore2 = noop2) => {
101233
101460
  const refusal2 = !projectIdRef.current ? "No project is open" : !isPreviewBooted(projectIdRef.current) ? "The preview for this project has not loaded yet" : null;
101234
101461
  if (refusal2) {
@@ -101246,7 +101473,7 @@ function useDomGeometryCommit({
101246
101473
  },
101247
101474
  [pending2, showToast]
101248
101475
  );
101249
- return useMemo91(
101476
+ return useMemo92(
101250
101477
  () => ({
101251
101478
  commitPathOffset: (selection, next, modifiers) => saved2(() => handleGsapAwarePathOffsetCommit(selection, next, modifiers)),
101252
101479
  commitGroupPathOffset: (updates) => saved2(() => handleGsapAwareGroupPathOffsetCommit(updates)),