@twick/studio 0.15.13 → 0.15.15

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 (35) hide show
  1. package/README.md +2 -2
  2. package/dist/components/container/captions-panel-container.d.ts +1 -0
  3. package/dist/components/container/element-panel-container.d.ts +1 -1
  4. package/dist/components/container/properties-panel-container.d.ts +7 -7
  5. package/dist/components/panel/captions-panel.d.ts +46 -0
  6. package/dist/components/panel/circle-panel.d.ts +1 -1
  7. package/dist/components/panel/rect-panel.d.ts +1 -1
  8. package/dist/components/panel/text-panel.d.ts +1 -1
  9. package/dist/components/properties/{subtitlte-prop.d.ts → caption-prop.d.ts} +4 -4
  10. package/dist/components/properties/generate-captions.d.ts +13 -0
  11. package/dist/components/properties/property-row.d.ts +10 -0
  12. package/dist/components/properties/text-props.d.ts +3 -0
  13. package/dist/helpers/generate-captions.service.d.ts +21 -0
  14. package/dist/helpers/volume-db.d.ts +22 -0
  15. package/dist/hooks/use-captions-panel.d.ts +13 -0
  16. package/dist/hooks/use-generate-captions.d.ts +9 -0
  17. package/dist/hooks/use-studio-operation.d.ts +4 -4
  18. package/dist/hooks/use-text-panel.d.ts +6 -0
  19. package/dist/index.d.ts +10 -13
  20. package/dist/index.js +1209 -1046
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +1194 -1034
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/studio.css +521 -162
  25. package/dist/types/index.d.ts +18 -16
  26. package/package.json +14 -12
  27. package/dist/components/container/icon-panel-container.d.ts +0 -3
  28. package/dist/components/container/subtitles-panel-container.d.ts +0 -1
  29. package/dist/components/panel/icon-panel.d.ts +0 -4
  30. package/dist/components/panel/subtitles-panel.d.ts +0 -46
  31. package/dist/components/properties/generate-subtitles.d.ts +0 -13
  32. package/dist/helpers/generate-subtitles.service.d.ts +0 -21
  33. package/dist/hooks/use-generate-subtitles.d.ts +0 -9
  34. package/dist/hooks/use-icon-panel.d.ts +0 -24
  35. package/dist/hooks/use-subtitles-panel.d.ts +0 -13
package/dist/index.mjs CHANGED
@@ -3,9 +3,9 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
5
5
  import { forwardRef, createElement, useState, useEffect, useRef, createContext, useContext, useCallback, useMemo } from "react";
6
- import { useTimelineContext, TrackElement, Track, ImageElement, AudioElement, VideoElement, TextElement, IconElement, RectElement, CircleElement, CAPTION_STYLE, CaptionElement, ElementTextEffect, ElementAnimation, PLAYER_STATE } from "@twick/timeline";
7
- import { AudioElement as AudioElement2, CAPTION_COLOR, CAPTION_FONT, CAPTION_STYLE as CAPTION_STYLE2, CAPTION_STYLE_OPTIONS, CaptionElement as CaptionElement2, CircleElement as CircleElement2, ElementAdder, ElementAnimation as ElementAnimation2, ElementCloner, ElementDeserializer, ElementFrameEffect, ElementRemover, ElementSerializer, ElementSplitter, ElementTextEffect as ElementTextEffect2, ElementUpdater, ElementValidator, INITIAL_TIMELINE_DATA, IconElement as IconElement2, ImageElement as ImageElement2, PROCESS_STATE, RectElement as RectElement2, TIMELINE_ACTION, TIMELINE_ELEMENT_TYPE, TextElement as TextElement2, TimelineEditor, TimelineProvider, Track as Track2, TrackElement as TrackElement2, VideoElement as VideoElement2, WORDS_PER_PHRASE, generateShortUuid, getCurrentElements, getTotalDuration, isElementId, isTrackId } from "@twick/timeline";
8
- import VideoEditor, { useEditorManager, BrowserMediaManager, AVAILABLE_TEXT_FONTS, TEXT_EFFECTS, ANIMATIONS } from "@twick/video-editor";
6
+ import { useTimelineContext, TrackElement, Track, ImageElement, AudioElement, VideoElement, TextElement, RectElement, CircleElement, CAPTION_STYLE, CaptionElement, ElementTextEffect, ElementAnimation, PLAYER_STATE } from "@twick/timeline";
7
+ import { AudioElement as AudioElement2, CAPTION_COLOR, CAPTION_FONT, CAPTION_STYLE as CAPTION_STYLE2, CAPTION_STYLE_OPTIONS, CaptionElement as CaptionElement2, CircleElement as CircleElement2, ElementAdder, ElementAnimation as ElementAnimation2, ElementCloner, ElementDeserializer, ElementFrameEffect, ElementRemover, ElementSerializer, ElementSplitter, ElementTextEffect as ElementTextEffect2, ElementUpdater, ElementValidator, INITIAL_TIMELINE_DATA, IconElement, ImageElement as ImageElement2, PROCESS_STATE, RectElement as RectElement2, TIMELINE_ACTION, TIMELINE_ELEMENT_TYPE, TextElement as TextElement2, TimelineEditor, TimelineProvider, Track as Track2, TrackElement as TrackElement2, VideoElement as VideoElement2, WORDS_PER_PHRASE, generateShortUuid, getCurrentElements, getTotalDuration, isElementId, isTrackId, useTimelineContext as useTimelineContext2 } from "@twick/timeline";
8
+ import VideoEditor, { useEditorManager, BrowserMediaManager, TIMELINE_DROP_MEDIA_TYPE, AVAILABLE_TEXT_FONTS, TEXT_EFFECTS, ANIMATIONS } from "@twick/video-editor";
9
9
  import { ANIMATIONS as ANIMATIONS2, BaseMediaManager, BrowserMediaManager as BrowserMediaManager2, PlayerControls, TEXT_EFFECTS as TEXT_EFFECTS2, TimelineManager, default as default2, animationGifs, getAnimationGif, setElementColors, useEditorManager as useEditorManager2, usePlayerControl, useTimelineControl } from "@twick/video-editor";
10
10
  import { useLivePlayerContext } from "@twick/live-player";
11
11
  import { LivePlayer, LivePlayerProvider, PLAYER_STATE as PLAYER_STATE2, generateId, getBaseProject, useLivePlayerContext as useLivePlayerContext2 } from "@twick/live-player";
@@ -114,49 +114,103 @@ const createLucideIcon = (iconName, iconNode) => {
114
114
  * This source code is licensed under the ISC license.
115
115
  * See the LICENSE file in the root directory of this source tree.
116
116
  */
117
- const __iconNode$u = [
118
- ["rect", { width: "18", height: "14", x: "3", y: "5", rx: "2", ry: "2", key: "12ruh7" }],
119
- ["path", { d: "M7 15h4M15 15h2M7 11h2M13 11h4", key: "1ueiar" }]
117
+ const __iconNode$y = [
118
+ ["path", { d: "M17 12H7", key: "16if0g" }],
119
+ ["path", { d: "M19 18H5", key: "18s9l3" }],
120
+ ["path", { d: "M21 6H3", key: "1jwq7v" }]
120
121
  ];
121
- const Captions = createLucideIcon("captions", __iconNode$u);
122
+ const AlignCenter = createLucideIcon("align-center", __iconNode$y);
122
123
  /**
123
124
  * @license lucide-react v0.511.0 - ISC
124
125
  *
125
126
  * This source code is licensed under the ISC license.
126
127
  * See the LICENSE file in the root directory of this source tree.
127
128
  */
128
- const __iconNode$t = [
129
+ const __iconNode$x = [
130
+ ["path", { d: "M15 12H3", key: "6jk70r" }],
131
+ ["path", { d: "M17 18H3", key: "1amg6g" }],
132
+ ["path", { d: "M21 6H3", key: "1jwq7v" }]
133
+ ];
134
+ const AlignLeft = createLucideIcon("align-left", __iconNode$x);
135
+ /**
136
+ * @license lucide-react v0.511.0 - ISC
137
+ *
138
+ * This source code is licensed under the ISC license.
139
+ * See the LICENSE file in the root directory of this source tree.
140
+ */
141
+ const __iconNode$w = [
142
+ ["path", { d: "M21 12H9", key: "dn1m92" }],
143
+ ["path", { d: "M21 18H7", key: "1ygte8" }],
144
+ ["path", { d: "M21 6H3", key: "1jwq7v" }]
145
+ ];
146
+ const AlignRight = createLucideIcon("align-right", __iconNode$w);
147
+ /**
148
+ * @license lucide-react v0.511.0 - ISC
149
+ *
150
+ * This source code is licensed under the ISC license.
151
+ * See the LICENSE file in the root directory of this source tree.
152
+ */
153
+ const __iconNode$v = [
154
+ [
155
+ "path",
156
+ { d: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8", key: "mg9rjx" }
157
+ ]
158
+ ];
159
+ const Bold = createLucideIcon("bold", __iconNode$v);
160
+ /**
161
+ * @license lucide-react v0.511.0 - ISC
162
+ *
163
+ * This source code is licensed under the ISC license.
164
+ * See the LICENSE file in the root directory of this source tree.
165
+ */
166
+ const __iconNode$u = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
167
+ const ChevronDown = createLucideIcon("chevron-down", __iconNode$u);
168
+ /**
169
+ * @license lucide-react v0.511.0 - ISC
170
+ *
171
+ * This source code is licensed under the ISC license.
172
+ * See the LICENSE file in the root directory of this source tree.
173
+ */
174
+ const __iconNode$t = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]];
175
+ const ChevronRight = createLucideIcon("chevron-right", __iconNode$t);
176
+ /**
177
+ * @license lucide-react v0.511.0 - ISC
178
+ *
179
+ * This source code is licensed under the ISC license.
180
+ * See the LICENSE file in the root directory of this source tree.
181
+ */
182
+ const __iconNode$s = [
129
183
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
130
184
  ["path", { d: "m9 12 2 2 4-4", key: "dzmm74" }]
131
185
  ];
132
- const CircleCheck = createLucideIcon("circle-check", __iconNode$t);
186
+ const CircleCheck = createLucideIcon("circle-check", __iconNode$s);
133
187
  /**
134
188
  * @license lucide-react v0.511.0 - ISC
135
189
  *
136
190
  * This source code is licensed under the ISC license.
137
191
  * See the LICENSE file in the root directory of this source tree.
138
192
  */
139
- const __iconNode$s = [
193
+ const __iconNode$r = [
140
194
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
141
195
  ["path", { d: "m15 9-6 6", key: "1uzhvr" }],
142
196
  ["path", { d: "m9 9 6 6", key: "z0biqf" }]
143
197
  ];
144
- const CircleX = createLucideIcon("circle-x", __iconNode$s);
198
+ const CircleX = createLucideIcon("circle-x", __iconNode$r);
145
199
  /**
146
200
  * @license lucide-react v0.511.0 - ISC
147
201
  *
148
202
  * This source code is licensed under the ISC license.
149
203
  * See the LICENSE file in the root directory of this source tree.
150
204
  */
151
- const __iconNode$r = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
152
- const Circle = createLucideIcon("circle", __iconNode$r);
205
+ const __iconNode$q = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
206
+ const Circle = createLucideIcon("circle", __iconNode$q);
153
207
  /**
154
208
  * @license lucide-react v0.511.0 - ISC
155
209
  *
156
210
  * This source code is licensed under the ISC license.
157
211
  * See the LICENSE file in the root directory of this source tree.
158
212
  */
159
- const __iconNode$q = [
213
+ const __iconNode$p = [
160
214
  [
161
215
  "path",
162
216
  { d: "M20.2 6 3 11l-.9-2.4c-.3-1.1.3-2.2 1.3-2.5l13.5-4c1.1-.3 2.2.3 2.5 1.3Z", key: "1tn4o7" }
@@ -165,139 +219,172 @@ const __iconNode$q = [
165
219
  ["path", { d: "m12.4 3.4 3.1 4", key: "6hsd6n" }],
166
220
  ["path", { d: "M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z", key: "ltgou9" }]
167
221
  ];
168
- const Clapperboard = createLucideIcon("clapperboard", __iconNode$q);
222
+ const Clapperboard = createLucideIcon("clapperboard", __iconNode$p);
169
223
  /**
170
224
  * @license lucide-react v0.511.0 - ISC
171
225
  *
172
226
  * This source code is licensed under the ISC license.
173
227
  * See the LICENSE file in the root directory of this source tree.
174
228
  */
175
- const __iconNode$p = [
229
+ const __iconNode$o = [
176
230
  ["path", { d: "M12 15V3", key: "m9g1x1" }],
177
231
  ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
178
232
  ["path", { d: "m7 10 5 5 5-5", key: "brsn70" }]
179
233
  ];
180
- const Download = createLucideIcon("download", __iconNode$p);
234
+ const Download = createLucideIcon("download", __iconNode$o);
181
235
  /**
182
236
  * @license lucide-react v0.511.0 - ISC
183
237
  *
184
238
  * This source code is licensed under the ISC license.
185
239
  * See the LICENSE file in the root directory of this source tree.
186
240
  */
187
- const __iconNode$o = [
241
+ const __iconNode$n = [
188
242
  ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }],
189
243
  ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }]
190
244
  ];
191
- const File = createLucideIcon("file", __iconNode$o);
245
+ const File = createLucideIcon("file", __iconNode$n);
192
246
  /**
193
247
  * @license lucide-react v0.511.0 - ISC
194
248
  *
195
249
  * This source code is licensed under the ISC license.
196
250
  * See the LICENSE file in the root directory of this source tree.
197
251
  */
198
- const __iconNode$n = [
252
+ const __iconNode$m = [
199
253
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
200
254
  ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
201
255
  ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
202
256
  ];
203
- const Image = createLucideIcon("image", __iconNode$n);
257
+ const Image = createLucideIcon("image", __iconNode$m);
204
258
  /**
205
259
  * @license lucide-react v0.511.0 - ISC
206
260
  *
207
261
  * This source code is licensed under the ISC license.
208
262
  * See the LICENSE file in the root directory of this source tree.
209
263
  */
210
- const __iconNode$m = [
211
- ["path", { d: "M6 16c5 0 7-8 12-8a4 4 0 0 1 0 8c-5 0-7-8-12-8a4 4 0 1 0 0 8", key: "18ogeb" }]
264
+ const __iconNode$l = [
265
+ ["line", { x1: "19", x2: "10", y1: "4", y2: "4", key: "15jd3p" }],
266
+ ["line", { x1: "14", x2: "5", y1: "20", y2: "20", key: "bu0au3" }],
267
+ ["line", { x1: "15", x2: "9", y1: "4", y2: "20", key: "uljnxc" }]
212
268
  ];
213
- const Infinity = createLucideIcon("infinity", __iconNode$m);
269
+ const Italic = createLucideIcon("italic", __iconNode$l);
214
270
  /**
215
271
  * @license lucide-react v0.511.0 - ISC
216
272
  *
217
273
  * This source code is licensed under the ISC license.
218
274
  * See the LICENSE file in the root directory of this source tree.
219
275
  */
220
- const __iconNode$l = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
221
- const LoaderCircle = createLucideIcon("loader-circle", __iconNode$l);
276
+ const __iconNode$k = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
277
+ const LoaderCircle = createLucideIcon("loader-circle", __iconNode$k);
222
278
  /**
223
279
  * @license lucide-react v0.511.0 - ISC
224
280
  *
225
281
  * This source code is licensed under the ISC license.
226
282
  * See the LICENSE file in the root directory of this source tree.
227
283
  */
228
- const __iconNode$k = [
284
+ const __iconNode$j = [
229
285
  ["path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z", key: "1lielz" }]
230
286
  ];
231
- const MessageSquare = createLucideIcon("message-square", __iconNode$k);
287
+ const MessageSquare = createLucideIcon("message-square", __iconNode$j);
232
288
  /**
233
289
  * @license lucide-react v0.511.0 - ISC
234
290
  *
235
291
  * This source code is licensed under the ISC license.
236
292
  * See the LICENSE file in the root directory of this source tree.
237
293
  */
238
- const __iconNode$j = [
294
+ const __iconNode$i = [
295
+ ["circle", { cx: "8", cy: "18", r: "4", key: "1fc0mg" }],
296
+ ["path", { d: "M12 18V2l7 4", key: "g04rme" }]
297
+ ];
298
+ const Music2 = createLucideIcon("music-2", __iconNode$i);
299
+ /**
300
+ * @license lucide-react v0.511.0 - ISC
301
+ *
302
+ * This source code is licensed under the ISC license.
303
+ * See the LICENSE file in the root directory of this source tree.
304
+ */
305
+ const __iconNode$h = [
239
306
  ["path", { d: "M9 18V5l12-2v13", key: "1jmyc2" }],
240
307
  ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
241
308
  ["circle", { cx: "18", cy: "16", r: "3", key: "1hluhg" }]
242
309
  ];
243
- const Music = createLucideIcon("music", __iconNode$j);
310
+ const Music = createLucideIcon("music", __iconNode$h);
244
311
  /**
245
312
  * @license lucide-react v0.511.0 - ISC
246
313
  *
247
314
  * This source code is licensed under the ISC license.
248
315
  * See the LICENSE file in the root directory of this source tree.
249
316
  */
250
- const __iconNode$i = [
317
+ const __iconNode$g = [
251
318
  ["rect", { x: "14", y: "4", width: "4", height: "16", rx: "1", key: "zuxfzm" }],
252
319
  ["rect", { x: "6", y: "4", width: "4", height: "16", rx: "1", key: "1okwgv" }]
253
320
  ];
254
- const Pause = createLucideIcon("pause", __iconNode$i);
321
+ const Pause = createLucideIcon("pause", __iconNode$g);
255
322
  /**
256
323
  * @license lucide-react v0.511.0 - ISC
257
324
  *
258
325
  * This source code is licensed under the ISC license.
259
326
  * See the LICENSE file in the root directory of this source tree.
260
327
  */
261
- const __iconNode$h = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
262
- const Play = createLucideIcon("play", __iconNode$h);
328
+ const __iconNode$f = [["polygon", { points: "6 3 20 12 6 21 6 3", key: "1oa8hb" }]];
329
+ const Play = createLucideIcon("play", __iconNode$f);
263
330
  /**
264
331
  * @license lucide-react v0.511.0 - ISC
265
332
  *
266
333
  * This source code is licensed under the ISC license.
267
334
  * See the LICENSE file in the root directory of this source tree.
268
335
  */
269
- const __iconNode$g = [
336
+ const __iconNode$e = [
270
337
  ["path", { d: "M5 12h14", key: "1ays0h" }],
271
338
  ["path", { d: "M12 5v14", key: "s699le" }]
272
339
  ];
273
- const Plus = createLucideIcon("plus", __iconNode$g);
340
+ const Plus = createLucideIcon("plus", __iconNode$e);
274
341
  /**
275
342
  * @license lucide-react v0.511.0 - ISC
276
343
  *
277
344
  * This source code is licensed under the ISC license.
278
345
  * See the LICENSE file in the root directory of this source tree.
279
346
  */
280
- const __iconNode$f = [
347
+ const __iconNode$d = [
281
348
  ["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2", key: "9lu3g6" }]
282
349
  ];
283
- const RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode$f);
350
+ const RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode$d);
284
351
  /**
285
352
  * @license lucide-react v0.511.0 - ISC
286
353
  *
287
354
  * This source code is licensed under the ISC license.
288
355
  * See the LICENSE file in the root directory of this source tree.
289
356
  */
290
- const __iconNode$e = [
357
+ const __iconNode$c = [
291
358
  ["rect", { width: "12", height: "20", x: "6", y: "2", rx: "2", key: "1oxtiu" }]
292
359
  ];
293
- const RectangleVertical = createLucideIcon("rectangle-vertical", __iconNode$e);
360
+ const RectangleVertical = createLucideIcon("rectangle-vertical", __iconNode$c);
294
361
  /**
295
362
  * @license lucide-react v0.511.0 - ISC
296
363
  *
297
364
  * This source code is licensed under the ISC license.
298
365
  * See the LICENSE file in the root directory of this source tree.
299
366
  */
300
- const __iconNode$d = [
367
+ const __iconNode$b = [
368
+ [
369
+ "path",
370
+ {
371
+ d: "M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.41 2.41 0 0 1 0-3.4l2.6-2.6a2.41 2.41 0 0 1 3.4 0Z",
372
+ key: "icamh8"
373
+ }
374
+ ],
375
+ ["path", { d: "m14.5 12.5 2-2", key: "inckbg" }],
376
+ ["path", { d: "m11.5 9.5 2-2", key: "fmmyf7" }],
377
+ ["path", { d: "m8.5 6.5 2-2", key: "vc6u1g" }],
378
+ ["path", { d: "m17.5 15.5 2-2", key: "wo5hmg" }]
379
+ ];
380
+ const Ruler = createLucideIcon("ruler", __iconNode$b);
381
+ /**
382
+ * @license lucide-react v0.511.0 - ISC
383
+ *
384
+ * This source code is licensed under the ISC license.
385
+ * See the LICENSE file in the root directory of this source tree.
386
+ */
387
+ const __iconNode$a = [
301
388
  [
302
389
  "path",
303
390
  {
@@ -308,56 +395,28 @@ const __iconNode$d = [
308
395
  ["path", { d: "M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7", key: "1ydtos" }],
309
396
  ["path", { d: "M7 3v4a1 1 0 0 0 1 1h7", key: "t51u73" }]
310
397
  ];
311
- const Save = createLucideIcon("save", __iconNode$d);
398
+ const Save = createLucideIcon("save", __iconNode$a);
312
399
  /**
313
400
  * @license lucide-react v0.511.0 - ISC
314
401
  *
315
402
  * This source code is licensed under the ISC license.
316
403
  * See the LICENSE file in the root directory of this source tree.
317
404
  */
318
- const __iconNode$c = [
405
+ const __iconNode$9 = [
319
406
  ["circle", { cx: "6", cy: "6", r: "3", key: "1lh9wr" }],
320
407
  ["path", { d: "M8.12 8.12 12 12", key: "1alkpv" }],
321
408
  ["path", { d: "M20 4 8.12 15.88", key: "xgtan2" }],
322
409
  ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
323
410
  ["path", { d: "M14.8 14.8 20 20", key: "ptml3r" }]
324
411
  ];
325
- const Scissors = createLucideIcon("scissors", __iconNode$c);
326
- /**
327
- * @license lucide-react v0.511.0 - ISC
328
- *
329
- * This source code is licensed under the ISC license.
330
- * See the LICENSE file in the root directory of this source tree.
331
- */
332
- const __iconNode$b = [
333
- ["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
334
- ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
335
- ];
336
- const Search = createLucideIcon("search", __iconNode$b);
337
- /**
338
- * @license lucide-react v0.511.0 - ISC
339
- *
340
- * This source code is licensed under the ISC license.
341
- * See the LICENSE file in the root directory of this source tree.
342
- */
343
- const __iconNode$a = [
344
- [
345
- "path",
346
- {
347
- d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",
348
- key: "1qme2f"
349
- }
350
- ],
351
- ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
352
- ];
353
- const Settings = createLucideIcon("settings", __iconNode$a);
412
+ const Scissors = createLucideIcon("scissors", __iconNode$9);
354
413
  /**
355
414
  * @license lucide-react v0.511.0 - ISC
356
415
  *
357
416
  * This source code is licensed under the ISC license.
358
417
  * See the LICENSE file in the root directory of this source tree.
359
418
  */
360
- const __iconNode$9 = [
419
+ const __iconNode$8 = [
361
420
  [
362
421
  "path",
363
422
  {
@@ -370,62 +429,62 @@ const __iconNode$9 = [
370
429
  ["path", { d: "M4 17v2", key: "vumght" }],
371
430
  ["path", { d: "M5 18H3", key: "zchphs" }]
372
431
  ];
373
- const Sparkles = createLucideIcon("sparkles", __iconNode$9);
432
+ const Sparkles = createLucideIcon("sparkles", __iconNode$8);
374
433
  /**
375
434
  * @license lucide-react v0.511.0 - ISC
376
435
  *
377
436
  * This source code is licensed under the ISC license.
378
437
  * See the LICENSE file in the root directory of this source tree.
379
438
  */
380
- const __iconNode$8 = [
439
+ const __iconNode$7 = [
381
440
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }]
382
441
  ];
383
- const Square = createLucideIcon("square", __iconNode$8);
442
+ const Square = createLucideIcon("square", __iconNode$7);
384
443
  /**
385
444
  * @license lucide-react v0.511.0 - ISC
386
445
  *
387
446
  * This source code is licensed under the ISC license.
388
447
  * See the LICENSE file in the root directory of this source tree.
389
448
  */
390
- const __iconNode$7 = [
449
+ const __iconNode$6 = [
391
450
  ["path", { d: "M3 6h18", key: "d0wm0j" }],
392
451
  ["path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6", key: "4alrt4" }],
393
452
  ["path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2", key: "v07s0e" }],
394
453
  ["line", { x1: "10", x2: "10", y1: "11", y2: "17", key: "1uufr5" }],
395
454
  ["line", { x1: "14", x2: "14", y1: "11", y2: "17", key: "xtxkd" }]
396
455
  ];
397
- const Trash2 = createLucideIcon("trash-2", __iconNode$7);
456
+ const Trash2 = createLucideIcon("trash-2", __iconNode$6);
398
457
  /**
399
458
  * @license lucide-react v0.511.0 - ISC
400
459
  *
401
460
  * This source code is licensed under the ISC license.
402
461
  * See the LICENSE file in the root directory of this source tree.
403
462
  */
404
- const __iconNode$6 = [
463
+ const __iconNode$5 = [
405
464
  ["path", { d: "M12 4v16", key: "1654pz" }],
406
465
  ["path", { d: "M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2", key: "e0r10z" }],
407
466
  ["path", { d: "M9 20h6", key: "s66wpe" }]
408
467
  ];
409
- const Type = createLucideIcon("type", __iconNode$6);
468
+ const Type = createLucideIcon("type", __iconNode$5);
410
469
  /**
411
470
  * @license lucide-react v0.511.0 - ISC
412
471
  *
413
472
  * This source code is licensed under the ISC license.
414
473
  * See the LICENSE file in the root directory of this source tree.
415
474
  */
416
- const __iconNode$5 = [
475
+ const __iconNode$4 = [
417
476
  ["path", { d: "M12 3v12", key: "1x0j5s" }],
418
477
  ["path", { d: "m17 8-5-5-5 5", key: "7q97r8" }],
419
478
  ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }]
420
479
  ];
421
- const Upload = createLucideIcon("upload", __iconNode$5);
480
+ const Upload = createLucideIcon("upload", __iconNode$4);
422
481
  /**
423
482
  * @license lucide-react v0.511.0 - ISC
424
483
  *
425
484
  * This source code is licensed under the ISC license.
426
485
  * See the LICENSE file in the root directory of this source tree.
427
486
  */
428
- const __iconNode$4 = [
487
+ const __iconNode$3 = [
429
488
  [
430
489
  "path",
431
490
  {
@@ -435,14 +494,14 @@ const __iconNode$4 = [
435
494
  ],
436
495
  ["rect", { x: "2", y: "6", width: "14", height: "12", rx: "2", key: "158x01" }]
437
496
  ];
438
- const Video = createLucideIcon("video", __iconNode$4);
497
+ const Video = createLucideIcon("video", __iconNode$3);
439
498
  /**
440
499
  * @license lucide-react v0.511.0 - ISC
441
500
  *
442
501
  * This source code is licensed under the ISC license.
443
502
  * See the LICENSE file in the root directory of this source tree.
444
503
  */
445
- const __iconNode$3 = [
504
+ const __iconNode$2 = [
446
505
  [
447
506
  "path",
448
507
  {
@@ -453,14 +512,14 @@ const __iconNode$3 = [
453
512
  ["path", { d: "M16 9a5 5 0 0 1 0 6", key: "1q6k2b" }],
454
513
  ["path", { d: "M19.364 18.364a9 9 0 0 0 0-12.728", key: "ijwkga" }]
455
514
  ];
456
- const Volume2 = createLucideIcon("volume-2", __iconNode$3);
515
+ const Volume2 = createLucideIcon("volume-2", __iconNode$2);
457
516
  /**
458
517
  * @license lucide-react v0.511.0 - ISC
459
518
  *
460
519
  * This source code is licensed under the ISC license.
461
520
  * See the LICENSE file in the root directory of this source tree.
462
521
  */
463
- const __iconNode$2 = [
522
+ const __iconNode$1 = [
464
523
  [
465
524
  "path",
466
525
  {
@@ -471,14 +530,14 @@ const __iconNode$2 = [
471
530
  ["line", { x1: "22", x2: "16", y1: "9", y2: "15", key: "1ewh16" }],
472
531
  ["line", { x1: "16", x2: "22", y1: "9", y2: "15", key: "5ykzw1" }]
473
532
  ];
474
- const VolumeX = createLucideIcon("volume-x", __iconNode$2);
533
+ const VolumeX = createLucideIcon("volume-x", __iconNode$1);
475
534
  /**
476
535
  * @license lucide-react v0.511.0 - ISC
477
536
  *
478
537
  * This source code is licensed under the ISC license.
479
538
  * See the LICENSE file in the root directory of this source tree.
480
539
  */
481
- const __iconNode$1 = [
540
+ const __iconNode = [
482
541
  [
483
542
  "path",
484
543
  {
@@ -494,41 +553,22 @@ const __iconNode$1 = [
494
553
  ["path", { d: "M21 16h-4", key: "1cnmox" }],
495
554
  ["path", { d: "M11 3H9", key: "1obp7u" }]
496
555
  ];
497
- const WandSparkles = createLucideIcon("wand-sparkles", __iconNode$1);
498
- /**
499
- * @license lucide-react v0.511.0 - ISC
500
- *
501
- * This source code is licensed under the ISC license.
502
- * See the LICENSE file in the root directory of this source tree.
503
- */
504
- const __iconNode = [
505
- [
506
- "path",
507
- {
508
- d: "M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",
509
- key: "1xq2db"
510
- }
511
- ]
512
- ];
513
- const Zap = createLucideIcon("zap", __iconNode);
556
+ const WandSparkles = createLucideIcon("wand-sparkles", __iconNode);
514
557
  const toolCategories = [
515
- { id: "video", name: "Video", icon: "Video", description: "Video" },
516
- { id: "image", name: "Image", icon: "Image", description: "Image" },
517
- { id: "audio", name: "Audio", icon: "Audio", description: "Audio" },
518
- { id: "text", name: "Text", icon: "Type", description: "Add text elements", shortcut: "T" },
519
- { id: "icon", name: "Icons", icon: "Icon", description: "Icon Element", shortcut: "I" },
520
- { id: "circle", name: "Circle", icon: "Circle", description: "Circle Element", shortcut: "C" },
521
- { id: "rect", name: "Rect", icon: "Rect", description: "Rect Element" },
522
- { id: "subtitle", name: "Subtitles", icon: "MessageSquare", description: "Manage subtitles", shortcut: "S" }
558
+ { id: "video", name: "Video", icon: "Video", description: "Add a video element" },
559
+ { id: "image", name: "Image", icon: "Image", description: "Add an image element" },
560
+ { id: "audio", name: "Audio", icon: "Audio", description: "Add an audio element" },
561
+ { id: "text", name: "Text", icon: "Type", description: "Add text elements" },
562
+ { id: "circle", name: "Circle", icon: "Circle", description: "Add a circle element" },
563
+ { id: "rect", name: "Rect", icon: "Rect", description: "Add a rectangle element" },
564
+ { id: "caption", name: "Caption", icon: "MessageSquare", description: "Manage captions" }
523
565
  ];
524
- const getIcon$1 = (iconName) => {
566
+ const getIcon = (iconName) => {
525
567
  switch (iconName) {
526
568
  case "Plus":
527
569
  return Plus;
528
570
  case "Type":
529
571
  return Type;
530
- case "Icon":
531
- return Infinity;
532
572
  case "Upload":
533
573
  return Upload;
534
574
  case "Square":
@@ -554,14 +594,16 @@ function Toolbar({ selectedTool, setSelectedTool }) {
554
594
  setSelectedTool(toolId);
555
595
  };
556
596
  return /* @__PURE__ */ jsx("div", { className: "sidebar", children: toolCategories.map((tool) => {
557
- const Icon2 = getIcon$1(tool.icon);
597
+ const Icon2 = getIcon(tool.icon);
558
598
  const isSelected = selectedTool === tool.id;
599
+ const tooltipText = `${tool.name}${tool.shortcut ? ` (${tool.shortcut})` : ""}`;
559
600
  return /* @__PURE__ */ jsxs(
560
601
  "div",
561
602
  {
562
603
  onClick: () => handleToolSelect(tool.id),
563
604
  className: `toolbar-btn ${isSelected ? "active" : ""}`,
564
- title: `${tool.name}${tool.shortcut ? ` (${tool.shortcut})` : ""}`,
605
+ title: tooltipText,
606
+ "data-tooltip": tooltipText,
565
607
  children: [
566
608
  /* @__PURE__ */ jsx(Icon2, { className: "icon-sm" }),
567
609
  /* @__PURE__ */ jsx("span", { className: "toolbar-label", children: tool.name })
@@ -608,28 +650,29 @@ const StudioHeader = ({
608
650
  return /* @__PURE__ */ jsxs("header", { className: "header", children: [
609
651
  /* @__PURE__ */ jsxs("div", { className: "flex-container", children: [
610
652
  /* @__PURE__ */ jsx(Clapperboard, { className: "icon-lg accent-purple" }),
611
- /* @__PURE__ */ jsx("h1", { className: "text-gradient", children: "Twick Studio" })
612
- ] }),
613
- /* @__PURE__ */ jsxs("div", { className: "flex-container", style: { gap: "0.5rem" }, children: [
614
- /* @__PURE__ */ jsx("span", { className: "text-sm opacity-80", children: "Orientation" }),
615
- /* @__PURE__ */ jsx(
616
- "button",
617
- {
618
- className: `btn-ghost ${orientation === "vertical" ? "btn-primary" : ""}`,
619
- title: "Portrait (720×1280)",
620
- onClick: () => handleOrientationChange("vertical"),
621
- children: /* @__PURE__ */ jsx(RectangleVertical, { className: "icon-sm" })
622
- }
623
- ),
624
- /* @__PURE__ */ jsx(
625
- "button",
626
- {
627
- className: `btn-ghost ${orientation === "horizontal" ? "btn-primary" : ""}`,
628
- title: "Landscape (1280×720)",
629
- onClick: () => handleOrientationChange("horizontal"),
630
- children: /* @__PURE__ */ jsx(RectangleHorizontal, { className: "icon-sm" })
631
- }
632
- )
653
+ /* @__PURE__ */ jsx("h1", { className: "text-gradient", children: "Twick Studio" }),
654
+ /* @__PURE__ */ jsx("div", { className: "header-separator" }),
655
+ /* @__PURE__ */ jsxs("div", { className: "flex-container", style: { gap: "0.5rem" }, children: [
656
+ /* @__PURE__ */ jsx("span", { className: "text-sm opacity-80", children: "Orientation" }),
657
+ /* @__PURE__ */ jsx(
658
+ "button",
659
+ {
660
+ className: `btn-ghost ${orientation === "vertical" ? "btn-primary" : ""}`,
661
+ title: "Portrait (720×1280)",
662
+ onClick: () => handleOrientationChange("vertical"),
663
+ children: /* @__PURE__ */ jsx(RectangleVertical, { className: "icon-sm" })
664
+ }
665
+ ),
666
+ /* @__PURE__ */ jsx(
667
+ "button",
668
+ {
669
+ className: `btn-ghost ${orientation === "horizontal" ? "btn-primary" : ""}`,
670
+ title: "Landscape (1280×720)",
671
+ onClick: () => handleOrientationChange("horizontal"),
672
+ children: /* @__PURE__ */ jsx(RectangleHorizontal, { className: "icon-sm" })
673
+ }
674
+ )
675
+ ] })
633
676
  ] }),
634
677
  /* @__PURE__ */ jsxs("div", { className: "flex-container", children: [
635
678
  /* @__PURE__ */ jsxs(
@@ -1181,8 +1224,16 @@ const AudioPanel = ({
1181
1224
  return /* @__PURE__ */ jsx(
1182
1225
  "div",
1183
1226
  {
1227
+ draggable: true,
1184
1228
  onDoubleClick: () => onItemSelect(item),
1185
- className: "media-list-item",
1229
+ onDragStart: (e) => {
1230
+ e.dataTransfer.setData(
1231
+ TIMELINE_DROP_MEDIA_TYPE,
1232
+ JSON.stringify({ type: "audio", url: item.url })
1233
+ );
1234
+ e.dataTransfer.effectAllowed = "copy";
1235
+ },
1236
+ className: "media-list-item media-item-draggable",
1186
1237
  children: /* @__PURE__ */ jsxs("div", { className: "media-list-content", children: [
1187
1238
  /* @__PURE__ */ jsx(
1188
1239
  "button",
@@ -1284,18 +1335,26 @@ function ImagePanel({
1284
1335
  /* @__PURE__ */ jsx("div", { className: "media-grid", children: (items || []).map((item) => /* @__PURE__ */ jsxs(
1285
1336
  "div",
1286
1337
  {
1338
+ draggable: true,
1287
1339
  onDoubleClick: () => onItemSelect(item),
1288
- className: "media-item",
1340
+ onDragStart: (e) => {
1341
+ e.dataTransfer.setData(
1342
+ TIMELINE_DROP_MEDIA_TYPE,
1343
+ JSON.stringify({ type: "image", url: item.url })
1344
+ );
1345
+ e.dataTransfer.effectAllowed = "copy";
1346
+ },
1347
+ className: "media-item media-item-draggable",
1289
1348
  children: [
1290
1349
  /* @__PURE__ */ jsx("img", { src: item.url, alt: "", className: "media-item-content" }),
1291
- /* @__PURE__ */ jsx("div", { className: "media-actions", children: /* @__PURE__ */ jsx(
1350
+ /* @__PURE__ */ jsx("div", { className: "media-actions media-actions-corner", children: /* @__PURE__ */ jsx(
1292
1351
  "button",
1293
1352
  {
1294
1353
  onClick: (e) => {
1295
1354
  e.stopPropagation();
1296
1355
  onItemSelect(item, true);
1297
1356
  },
1298
- className: "media-action-btn media-action-btn-primary",
1357
+ className: "media-action-btn",
1299
1358
  children: /* @__PURE__ */ jsx(Plus, { className: "icon-sm" })
1300
1359
  }
1301
1360
  ) })
@@ -1413,8 +1472,16 @@ function VideoPanel({
1413
1472
  /* @__PURE__ */ jsx("div", { className: "media-grid", children: (items || []).map((item) => /* @__PURE__ */ jsxs(
1414
1473
  "div",
1415
1474
  {
1475
+ draggable: true,
1416
1476
  onDoubleClick: () => onItemSelect(item),
1417
- className: "media-item",
1477
+ onDragStart: (e) => {
1478
+ e.dataTransfer.setData(
1479
+ TIMELINE_DROP_MEDIA_TYPE,
1480
+ JSON.stringify({ type: "video", url: item.url })
1481
+ );
1482
+ e.dataTransfer.effectAllowed = "copy";
1483
+ },
1484
+ className: "media-item media-item-draggable",
1418
1485
  children: [
1419
1486
  /* @__PURE__ */ jsx(
1420
1487
  "video",
@@ -1431,32 +1498,34 @@ function VideoPanel({
1431
1498
  }
1432
1499
  }
1433
1500
  ),
1434
- /* @__PURE__ */ jsx("div", { className: "media-actions", children: /* @__PURE__ */ jsx(
1435
- "button",
1436
- {
1437
- onClick: (e) => {
1438
- e.stopPropagation();
1439
- onItemSelect(item, true);
1440
- },
1441
- className: "media-action-btn media-action-btn-primary",
1442
- children: /* @__PURE__ */ jsx(Plus, { className: "icon-sm" })
1443
- }
1444
- ) }),
1445
- /* @__PURE__ */ jsx("div", { className: "media-actions media-actions-corner", children: /* @__PURE__ */ jsx(
1446
- "button",
1447
- {
1448
- onClick: (e) => {
1449
- var _a, _b;
1450
- e.stopPropagation();
1451
- const videoEl = (_b = (_a = e.currentTarget.parentElement) == null ? void 0 : _a.parentElement) == null ? void 0 : _b.querySelector("video");
1452
- if (videoEl) {
1453
- togglePlayPause(item, videoEl);
1454
- }
1455
- },
1456
- className: "media-action-btn",
1457
- children: playingVideo === item.id ? /* @__PURE__ */ jsx(Pause, { className: "icon-sm" }) : /* @__PURE__ */ jsx(Play, { className: "icon-sm" })
1458
- }
1459
- ) })
1501
+ /* @__PURE__ */ jsxs("div", { className: "media-actions media-actions-corner", children: [
1502
+ /* @__PURE__ */ jsx(
1503
+ "button",
1504
+ {
1505
+ onClick: (e) => {
1506
+ var _a, _b;
1507
+ e.stopPropagation();
1508
+ const videoEl = (_b = (_a = e.currentTarget.parentElement) == null ? void 0 : _a.parentElement) == null ? void 0 : _b.querySelector("video");
1509
+ if (videoEl) {
1510
+ togglePlayPause(item, videoEl);
1511
+ }
1512
+ },
1513
+ className: "media-action-btn",
1514
+ children: playingVideo === item.id ? /* @__PURE__ */ jsx(Pause, { className: "icon-sm" }) : /* @__PURE__ */ jsx(Play, { className: "icon-sm" })
1515
+ }
1516
+ ),
1517
+ /* @__PURE__ */ jsx(
1518
+ "button",
1519
+ {
1520
+ onClick: (e) => {
1521
+ e.stopPropagation();
1522
+ onItemSelect(item, true);
1523
+ },
1524
+ className: "media-action-btn",
1525
+ children: /* @__PURE__ */ jsx(Plus, { className: "icon-sm" })
1526
+ }
1527
+ )
1528
+ ] })
1460
1529
  ]
1461
1530
  },
1462
1531
  item.id
@@ -1527,6 +1596,9 @@ function TextPanel({
1527
1596
  applyShadow,
1528
1597
  shadowColor,
1529
1598
  strokeWidth,
1599
+ applyBackground,
1600
+ backgroundColor,
1601
+ backgroundOpacity,
1530
1602
  fonts,
1531
1603
  operation,
1532
1604
  setTextContent,
@@ -1539,6 +1611,9 @@ function TextPanel({
1539
1611
  setApplyShadow,
1540
1612
  setShadowColor,
1541
1613
  setStrokeWidth,
1614
+ setApplyBackground,
1615
+ setBackgroundColor,
1616
+ setBackgroundOpacity,
1542
1617
  handleApplyChanges
1543
1618
  }) {
1544
1619
  return /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
@@ -1707,7 +1782,70 @@ function TextPanel({
1707
1782
  /* @__PURE__ */ jsx("span", { className: "slider-value", children: strokeWidth })
1708
1783
  ] })
1709
1784
  ] }),
1710
- /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx("button", { onClick: handleApplyChanges, className: "btn-primary w-full", children: operation }) })
1785
+ /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
1786
+ /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Background" }),
1787
+ /* @__PURE__ */ jsxs("div", { className: "color-section", children: [
1788
+ /* @__PURE__ */ jsx("div", { className: "checkbox-control", children: /* @__PURE__ */ jsxs("label", { className: "checkbox-label", children: [
1789
+ /* @__PURE__ */ jsx(
1790
+ "input",
1791
+ {
1792
+ type: "checkbox",
1793
+ checked: applyBackground,
1794
+ onChange: (e) => setApplyBackground(e.target.checked),
1795
+ className: "checkbox-purple"
1796
+ }
1797
+ ),
1798
+ "Apply Background"
1799
+ ] }) }),
1800
+ applyBackground && /* @__PURE__ */ jsxs(Fragment, { children: [
1801
+ /* @__PURE__ */ jsxs("div", { className: "color-control", children: [
1802
+ /* @__PURE__ */ jsx("label", { className: "label-small", children: "Background Color" }),
1803
+ /* @__PURE__ */ jsxs("div", { className: "color-inputs", children: [
1804
+ /* @__PURE__ */ jsx(
1805
+ "input",
1806
+ {
1807
+ type: "color",
1808
+ value: backgroundColor,
1809
+ onChange: (e) => setBackgroundColor(e.target.value),
1810
+ className: "color-picker"
1811
+ }
1812
+ ),
1813
+ /* @__PURE__ */ jsx(
1814
+ "input",
1815
+ {
1816
+ type: "text",
1817
+ value: backgroundColor,
1818
+ onChange: (e) => setBackgroundColor(e.target.value),
1819
+ className: "color-text"
1820
+ }
1821
+ )
1822
+ ] })
1823
+ ] }),
1824
+ /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
1825
+ /* @__PURE__ */ jsx("label", { className: "label-small", children: "Background Opacity" }),
1826
+ /* @__PURE__ */ jsxs("div", { className: "slider-container", children: [
1827
+ /* @__PURE__ */ jsx(
1828
+ "input",
1829
+ {
1830
+ type: "range",
1831
+ min: "0",
1832
+ max: "1",
1833
+ step: 0.1,
1834
+ value: backgroundOpacity,
1835
+ onChange: (e) => setBackgroundOpacity(Number(e.target.value)),
1836
+ className: "slider-purple"
1837
+ }
1838
+ ),
1839
+ /* @__PURE__ */ jsxs("span", { className: "slider-value", children: [
1840
+ Math.round(backgroundOpacity * 100),
1841
+ "%"
1842
+ ] })
1843
+ ] })
1844
+ ] })
1845
+ ] })
1846
+ ] })
1847
+ ] }),
1848
+ operation !== "Apply Changes" && /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx("button", { onClick: handleApplyChanges, className: "btn-primary w-full", children: operation }) })
1711
1849
  ] });
1712
1850
  }
1713
1851
  const DEFAULT_TEXT_PROPS = {
@@ -1741,53 +1879,134 @@ const useTextPanel = ({
1741
1879
  const [applyShadow, setApplyShadow] = useState(DEFAULT_TEXT_PROPS.applyShadow);
1742
1880
  const [shadowColor, setShadowColor] = useState(DEFAULT_TEXT_PROPS.shadowColor);
1743
1881
  const [strokeWidth, setStrokeWidth] = useState(DEFAULT_TEXT_PROPS.strokeWidth);
1882
+ const [applyBackground, setApplyBackground] = useState(false);
1883
+ const [backgroundColor, setBackgroundColor] = useState("#FACC15");
1884
+ const [backgroundOpacity, setBackgroundOpacity] = useState(1);
1744
1885
  const fonts = Object.values(AVAILABLE_TEXT_FONTS);
1745
- const handleApplyChanges = async () => {
1746
- let textElement;
1747
- if (selectedElement instanceof TextElement) {
1748
- textElement = selectedElement;
1749
- textElement.setText(textContent);
1750
- textElement.setFontSize(fontSize);
1751
- textElement.setFontFamily(selectedFont);
1752
- textElement.setFontWeight(isBold ? 700 : 400);
1753
- textElement.setFontStyle(isItalic ? "italic" : "normal");
1754
- textElement.setFill(textColor);
1755
- textElement.setStrokeColor(strokeColor);
1756
- textElement.setLineWidth(strokeWidth);
1757
- textElement.setTextAlign(DEFAULT_TEXT_PROPS.textAlign);
1758
- if (applyShadow) {
1759
- textElement.setProps({
1760
- ...textElement.getProps(),
1761
- shadowColor,
1762
- shadowOffset: DEFAULT_TEXT_PROPS.shadowOffset,
1763
- shadowBlur: DEFAULT_TEXT_PROPS.shadowBlur,
1764
- shadowOpacity: DEFAULT_TEXT_PROPS.shadowOpacity
1765
- });
1766
- } else {
1767
- textElement.setProps({
1768
- ...textElement.getProps(),
1769
- shadowColor: void 0,
1770
- shadowOffset: void 0,
1771
- shadowBlur: void 0,
1772
- shadowOpacity: void 0
1773
- });
1774
- }
1775
- updateElement(textElement);
1886
+ const applyLiveChangesToExistingText = (overrides = {}) => {
1887
+ if (!(selectedElement instanceof TextElement)) {
1888
+ return;
1889
+ }
1890
+ const textElement = selectedElement;
1891
+ const nextState = {
1892
+ textContent,
1893
+ fontSize,
1894
+ selectedFont,
1895
+ isBold,
1896
+ isItalic,
1897
+ textColor,
1898
+ strokeColor,
1899
+ strokeWidth,
1900
+ applyShadow,
1901
+ shadowColor,
1902
+ applyBackground,
1903
+ backgroundColor,
1904
+ backgroundOpacity,
1905
+ ...overrides
1906
+ };
1907
+ textElement.setText(nextState.textContent);
1908
+ textElement.setFontSize(nextState.fontSize);
1909
+ textElement.setFontFamily(nextState.selectedFont);
1910
+ textElement.setFontWeight(nextState.isBold ? 700 : 400);
1911
+ textElement.setFontStyle(nextState.isItalic ? "italic" : "normal");
1912
+ textElement.setFill(nextState.textColor);
1913
+ textElement.setStrokeColor(nextState.strokeColor);
1914
+ textElement.setLineWidth(nextState.strokeWidth);
1915
+ textElement.setTextAlign(DEFAULT_TEXT_PROPS.textAlign);
1916
+ const nextProps = { ...textElement.getProps() };
1917
+ if (nextState.applyShadow) {
1918
+ nextProps.shadowColor = nextState.shadowColor;
1919
+ nextProps.shadowOffset = DEFAULT_TEXT_PROPS.shadowOffset;
1920
+ nextProps.shadowBlur = DEFAULT_TEXT_PROPS.shadowBlur;
1921
+ nextProps.shadowOpacity = DEFAULT_TEXT_PROPS.shadowOpacity;
1776
1922
  } else {
1777
- textElement = new TextElement(textContent).setFontSize(fontSize).setFontFamily(selectedFont).setFontWeight(isBold ? 700 : 400).setFontStyle(isItalic ? "italic" : "normal").setFill(textColor).setStrokeColor(strokeColor).setLineWidth(strokeWidth).setTextAlign("center");
1778
- if (applyShadow) {
1779
- textElement.setProps({
1780
- ...textElement.getProps(),
1781
- shadowColor,
1782
- shadowOffset: DEFAULT_TEXT_PROPS.shadowOffset,
1783
- shadowBlur: DEFAULT_TEXT_PROPS.shadowBlur,
1784
- shadowOpacity: DEFAULT_TEXT_PROPS.shadowOpacity
1785
- });
1786
- }
1787
- await addElement(textElement);
1923
+ nextProps.shadowColor = void 0;
1924
+ nextProps.shadowOffset = void 0;
1925
+ nextProps.shadowBlur = void 0;
1926
+ nextProps.shadowOpacity = void 0;
1927
+ }
1928
+ if (nextState.applyBackground) {
1929
+ nextProps.backgroundColor = nextState.backgroundColor;
1930
+ nextProps.backgroundOpacity = nextState.backgroundOpacity;
1931
+ } else {
1932
+ nextProps.backgroundColor = void 0;
1933
+ nextProps.backgroundOpacity = void 0;
1788
1934
  }
1935
+ textElement.setProps(nextProps);
1936
+ updateElement(textElement);
1789
1937
  };
1790
- useEffect(() => {
1938
+ const handleTextContentChange = (text) => {
1939
+ setTextContent(text);
1940
+ applyLiveChangesToExistingText({ textContent: text });
1941
+ };
1942
+ const handleFontSizeChange = (size) => {
1943
+ setFontSize(size);
1944
+ applyLiveChangesToExistingText({ fontSize: size });
1945
+ };
1946
+ const handleSelectedFontChange = (font) => {
1947
+ setSelectedFont(font);
1948
+ applyLiveChangesToExistingText({ selectedFont: font });
1949
+ };
1950
+ const handleIsBoldChange = (bold) => {
1951
+ setIsBold(bold);
1952
+ applyLiveChangesToExistingText({ isBold: bold });
1953
+ };
1954
+ const handleIsItalicChange = (italic) => {
1955
+ setIsItalic(italic);
1956
+ applyLiveChangesToExistingText({ isItalic: italic });
1957
+ };
1958
+ const handleTextColorChange = (color) => {
1959
+ setTextColor(color);
1960
+ applyLiveChangesToExistingText({ textColor: color });
1961
+ };
1962
+ const handleStrokeColorChange = (color) => {
1963
+ setStrokeColor(color);
1964
+ applyLiveChangesToExistingText({ strokeColor: color });
1965
+ };
1966
+ const handleStrokeWidthChange = (width) => {
1967
+ setStrokeWidth(width);
1968
+ applyLiveChangesToExistingText({ strokeWidth: width });
1969
+ };
1970
+ const handleApplyShadowChange = (shadow) => {
1971
+ setApplyShadow(shadow);
1972
+ applyLiveChangesToExistingText({ applyShadow: shadow });
1973
+ };
1974
+ const handleShadowColorChange = (color) => {
1975
+ setShadowColor(color);
1976
+ applyLiveChangesToExistingText({ shadowColor: color });
1977
+ };
1978
+ const handleApplyBackgroundChange = (apply) => {
1979
+ setApplyBackground(apply);
1980
+ applyLiveChangesToExistingText({ applyBackground: apply });
1981
+ };
1982
+ const handleBackgroundColorChange = (color) => {
1983
+ setBackgroundColor(color);
1984
+ applyLiveChangesToExistingText({ backgroundColor: color });
1985
+ };
1986
+ const handleBackgroundOpacityChange = (opacity) => {
1987
+ setBackgroundOpacity(opacity);
1988
+ applyLiveChangesToExistingText({ backgroundOpacity: opacity });
1989
+ };
1990
+ const handleApplyChanges = async () => {
1991
+ if (selectedElement instanceof TextElement) {
1992
+ return;
1993
+ }
1994
+ const textElement = new TextElement(textContent).setFontSize(fontSize).setFontFamily(selectedFont).setFontWeight(isBold ? 700 : 400).setFontStyle(isItalic ? "italic" : "normal").setFill(textColor).setStrokeColor(strokeColor).setLineWidth(strokeWidth).setTextAlign("center");
1995
+ const nextProps = { ...textElement.getProps() };
1996
+ if (applyShadow) {
1997
+ nextProps.shadowColor = shadowColor;
1998
+ nextProps.shadowOffset = DEFAULT_TEXT_PROPS.shadowOffset;
1999
+ nextProps.shadowBlur = DEFAULT_TEXT_PROPS.shadowBlur;
2000
+ nextProps.shadowOpacity = DEFAULT_TEXT_PROPS.shadowOpacity;
2001
+ }
2002
+ if (applyBackground) {
2003
+ nextProps.backgroundColor = backgroundColor;
2004
+ nextProps.backgroundOpacity = backgroundOpacity;
2005
+ }
2006
+ textElement.setProps(nextProps);
2007
+ await addElement(textElement);
2008
+ };
2009
+ useEffect(() => {
1791
2010
  if (selectedElement instanceof TextElement) {
1792
2011
  setTextContent(selectedElement.getText());
1793
2012
  const textProps = selectedElement.getProps();
@@ -1803,6 +2022,12 @@ const useTextPanel = ({
1803
2022
  if (hasShadow) {
1804
2023
  setShadowColor(textProps.shadowColor ?? DEFAULT_TEXT_PROPS.shadowColor);
1805
2024
  }
2025
+ const hasBackground = textProps.backgroundColor != null && textProps.backgroundColor !== "";
2026
+ setApplyBackground(hasBackground);
2027
+ if (hasBackground) {
2028
+ setBackgroundColor(textProps.backgroundColor ?? "#FACC15");
2029
+ setBackgroundOpacity(textProps.backgroundOpacity ?? 1);
2030
+ }
1806
2031
  } else {
1807
2032
  setTextContent(DEFAULT_TEXT_PROPS.text);
1808
2033
  setFontSize(DEFAULT_TEXT_PROPS.fontSize);
@@ -1814,6 +2039,9 @@ const useTextPanel = ({
1814
2039
  setStrokeWidth(DEFAULT_TEXT_PROPS.strokeWidth);
1815
2040
  setApplyShadow(DEFAULT_TEXT_PROPS.applyShadow);
1816
2041
  setShadowColor(DEFAULT_TEXT_PROPS.shadowColor);
2042
+ setApplyBackground(false);
2043
+ setBackgroundColor("#FACC15");
2044
+ setBackgroundOpacity(1);
1817
2045
  }
1818
2046
  }, [selectedElement]);
1819
2047
  return {
@@ -1829,16 +2057,22 @@ const useTextPanel = ({
1829
2057
  strokeWidth,
1830
2058
  fonts,
1831
2059
  operation: selectedElement instanceof TextElement ? "Apply Changes" : "Add Text",
1832
- setTextContent,
1833
- setFontSize,
1834
- setSelectedFont,
1835
- setIsBold,
1836
- setIsItalic,
1837
- setTextColor,
1838
- setStrokeColor,
1839
- setApplyShadow,
1840
- setShadowColor,
1841
- setStrokeWidth,
2060
+ setTextContent: handleTextContentChange,
2061
+ setFontSize: handleFontSizeChange,
2062
+ setSelectedFont: handleSelectedFontChange,
2063
+ setIsBold: handleIsBoldChange,
2064
+ setIsItalic: handleIsItalicChange,
2065
+ setTextColor: handleTextColorChange,
2066
+ setStrokeColor: handleStrokeColorChange,
2067
+ setApplyShadow: handleApplyShadowChange,
2068
+ setShadowColor: handleShadowColorChange,
2069
+ setStrokeWidth: handleStrokeWidthChange,
2070
+ applyBackground,
2071
+ backgroundColor,
2072
+ backgroundOpacity,
2073
+ setApplyBackground: handleApplyBackgroundChange,
2074
+ setBackgroundColor: handleBackgroundColorChange,
2075
+ setBackgroundOpacity: handleBackgroundOpacityChange,
1842
2076
  handleApplyChanges
1843
2077
  };
1844
2078
  };
@@ -1846,227 +2080,14 @@ function TextPanelContainer(props) {
1846
2080
  const textPanelProps = useTextPanel(props);
1847
2081
  return /* @__PURE__ */ jsx(TextPanel, { ...textPanelProps });
1848
2082
  }
1849
- const SearchInput = ({
1850
- searchQuery,
1851
- setSearchQuery
1852
- }) => {
1853
- return /* @__PURE__ */ jsxs("div", { className: "search-container", children: [
1854
- /* @__PURE__ */ jsx(
1855
- "input",
1856
- {
1857
- type: "text",
1858
- placeholder: "Search media...",
1859
- value: searchQuery,
1860
- onChange: (e) => setSearchQuery(e.target.value),
1861
- className: "input search-input w-full"
1862
- }
1863
- ),
1864
- /* @__PURE__ */ jsx(Search, { className: "search-icon" })
1865
- ] });
1866
- };
1867
- function IconPanel({
1868
- icons,
1869
- loading,
1870
- totalIcons,
1871
- searchQuery,
1872
- handleSearch,
1873
- handleSelection,
1874
- handleDownloadIcon,
1875
- handleLoadMore
1876
- }) {
1877
- return /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
1878
- /* @__PURE__ */ jsx("div", { className: "panel-title", children: "Icon Library" }),
1879
- /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx(SearchInput, { searchQuery, setSearchQuery: handleSearch }) }),
1880
- /* @__PURE__ */ jsxs("div", { className: "media-content", children: [
1881
- totalIcons > 0 && /* @__PURE__ */ jsxs("div", { className: "media-count", children: [
1882
- "Showing ",
1883
- icons.length,
1884
- " of ",
1885
- totalIcons,
1886
- " icons"
1887
- ] }),
1888
- loading && icons.length === 0 ? /* @__PURE__ */ jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxs("div", { className: "empty-state-content", children: [
1889
- /* @__PURE__ */ jsx(LoaderCircle, { className: "empty-state-icon animate-spin" }),
1890
- /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: "Loading icons..." })
1891
- ] }) }) : /* @__PURE__ */ jsx("div", { className: "icon-grid", children: (icons || []).map((icon, index) => /* @__PURE__ */ jsxs("div", { className: "icon-item", children: [
1892
- /* @__PURE__ */ jsx(
1893
- "div",
1894
- {
1895
- onClick: () => handleSelection(icon),
1896
- className: "icon-content",
1897
- dangerouslySetInnerHTML: { __html: icon.svg }
1898
- }
1899
- ),
1900
- /* @__PURE__ */ jsxs("div", { className: "icon-actions", children: [
1901
- /* @__PURE__ */ jsx(
1902
- "button",
1903
- {
1904
- onClick: (e) => {
1905
- e.stopPropagation();
1906
- handleSelection(icon);
1907
- },
1908
- className: "icon-action-btn",
1909
- title: "Add to timeline",
1910
- children: /* @__PURE__ */ jsx(Plus, { className: "icon-sm" })
1911
- }
1912
- ),
1913
- /* @__PURE__ */ jsx(
1914
- "button",
1915
- {
1916
- onClick: (e) => {
1917
- e.stopPropagation();
1918
- handleDownloadIcon(icon);
1919
- },
1920
- className: "icon-action-btn",
1921
- title: "Download SVG",
1922
- children: /* @__PURE__ */ jsx(Download, { className: "icon-sm" })
1923
- }
1924
- )
1925
- ] }),
1926
- /* @__PURE__ */ jsx("div", { className: "icon-name", children: icon.name })
1927
- ] }, index)) }),
1928
- !loading && icons.length === 0 && searchQuery && /* @__PURE__ */ jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxs("div", { className: "empty-state-content", children: [
1929
- /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: "No icons found" }),
1930
- /* @__PURE__ */ jsx("p", { className: "empty-state-subtext", children: "Try a different search term" })
1931
- ] }) }),
1932
- !loading && totalIcons && icons.length < totalIcons && /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx(
1933
- "button",
1934
- {
1935
- onClick: handleLoadMore,
1936
- disabled: loading,
1937
- className: "btn-primary",
1938
- children: "Load More Icons"
1939
- }
1940
- ) })
1941
- ] })
1942
- ] });
1943
- }
1944
- const ICONS_PER_PAGE = 20;
1945
- const useIconPanel = ({
1946
- selectedElement,
1947
- addElement,
1948
- updateElement
1949
- }) => {
1950
- const [icons, setIcons] = useState([]);
1951
- const [loading, setLoading] = useState(false);
1952
- const [page, setPage] = useState(1);
1953
- const [hasMore, setHasMore] = useState(true);
1954
- const [totalIcons, setTotalIcons] = useState(0);
1955
- const [searchQuery, setSearchQuery] = useState("");
1956
- const currentQuery = useRef("");
1957
- const fetchIcons = async (query, reset = false) => {
1958
- try {
1959
- setLoading(true);
1960
- const newPage = reset ? 1 : page;
1961
- const start = (newPage - 1) * ICONS_PER_PAGE;
1962
- const url = `https://api.iconify.design/search?query=${query}&limit=${ICONS_PER_PAGE}&offset=${start}`;
1963
- const response = await fetch(url);
1964
- const data = await response.json();
1965
- const iconData = data.icons || [];
1966
- const total = data.total || 0;
1967
- setTotalIcons(total);
1968
- const formattedIcons = await Promise.all(
1969
- iconData.map(async (icon) => {
1970
- const svgUrl = `https://api.iconify.design/${icon}.svg`;
1971
- try {
1972
- const svgResponse = await fetch(svgUrl);
1973
- const svg = await svgResponse.text();
1974
- return { name: icon, svg };
1975
- } catch (e) {
1976
- console.error(`Error fetching SVG for ${icon}:`, e);
1977
- return null;
1978
- }
1979
- })
1980
- );
1981
- const validIcons = formattedIcons.filter((icon) => icon !== null);
1982
- if (reset) {
1983
- setIcons(validIcons);
1984
- } else {
1985
- setIcons([...icons, ...validIcons]);
1986
- }
1987
- setHasMore(start + validIcons.length < total);
1988
- if (!reset) {
1989
- setPage(newPage + 1);
1990
- } else {
1991
- setPage(2);
1992
- }
1993
- } catch (error) {
1994
- console.error("Error fetching icons:", error);
1995
- } finally {
1996
- setLoading(false);
1997
- }
1998
- };
1999
- useEffect(() => {
2000
- fetchIcons("media", true);
2001
- }, []);
2002
- const handleSearch = (query) => {
2003
- currentQuery.current = query;
2004
- setSearchQuery(query);
2005
- fetchIcons(query, true);
2006
- };
2007
- const handleSelection = (icon) => {
2008
- const svgBlob = new Blob([icon.svg], { type: "image/svg+xml" });
2009
- const url = URL.createObjectURL(svgBlob);
2010
- let iconElement;
2011
- if (selectedElement instanceof IconElement) {
2012
- iconElement = selectedElement;
2013
- iconElement.setSrc(url);
2014
- iconElement.setName(icon.name);
2015
- updateElement == null ? void 0 : updateElement(iconElement);
2016
- } else {
2017
- iconElement = new IconElement(url, {
2018
- width: 100,
2019
- height: 100
2020
- });
2021
- iconElement.setName(icon.name);
2022
- addElement == null ? void 0 : addElement(iconElement);
2023
- }
2024
- URL.revokeObjectURL(url);
2025
- };
2026
- const handleDownloadIcon = (icon) => {
2027
- const blob = new Blob([icon.svg], { type: "image/svg+xml" });
2028
- const url = URL.createObjectURL(blob);
2029
- const a = document.createElement("a");
2030
- a.href = url;
2031
- a.download = `${icon.name}.svg`;
2032
- document.body.appendChild(a);
2033
- a.click();
2034
- document.body.removeChild(a);
2035
- URL.revokeObjectURL(url);
2036
- };
2037
- const handleLoadMore = () => {
2038
- fetchIcons(currentQuery.current, false);
2039
- };
2040
- return {
2041
- icons,
2042
- loading,
2043
- hasMore,
2044
- totalIcons,
2045
- searchQuery,
2046
- handleSearch,
2047
- handleSelection,
2048
- handleDownloadIcon,
2049
- handleLoadMore
2050
- };
2051
- };
2052
- function IconPanelContainer(props) {
2053
- const iconPanelProps = useIconPanel({
2054
- selectedElement: props.selectedElement ?? null,
2055
- addElement: props.addElement,
2056
- updateElement: props.updateElement
2057
- });
2058
- return /* @__PURE__ */ jsx(IconPanel, { ...iconPanelProps });
2059
- }
2060
2083
  function RectPanel({
2061
2084
  cornerRadius,
2062
2085
  fillColor,
2063
- opacity,
2064
2086
  strokeColor,
2065
2087
  lineWidth,
2066
2088
  operation,
2067
2089
  setCornerRadius,
2068
2090
  setFillColor,
2069
- setOpacity,
2070
2091
  setStrokeColor,
2071
2092
  setLineWidth,
2072
2093
  handleApplyChanges
@@ -2116,26 +2137,6 @@ function RectPanel({
2116
2137
  )
2117
2138
  ] })
2118
2139
  ] }),
2119
- /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
2120
- /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Opacity" }),
2121
- /* @__PURE__ */ jsxs("div", { className: "slider-container", children: [
2122
- /* @__PURE__ */ jsx(
2123
- "input",
2124
- {
2125
- type: "range",
2126
- min: "0",
2127
- max: "100",
2128
- value: opacity,
2129
- onChange: (e) => setOpacity(Number(e.target.value)),
2130
- className: "slider-purple"
2131
- }
2132
- ),
2133
- /* @__PURE__ */ jsxs("span", { className: "slider-value", children: [
2134
- opacity,
2135
- "%"
2136
- ] })
2137
- ] })
2138
- ] }),
2139
2140
  /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
2140
2141
  /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Stroke Color" }),
2141
2142
  /* @__PURE__ */ jsxs("div", { className: "color-inputs", children: [
@@ -2179,7 +2180,7 @@ function RectPanel({
2179
2180
  ] })
2180
2181
  ] })
2181
2182
  ] }),
2182
- /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx(
2183
+ operation !== "Apply Changes" && /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx(
2183
2184
  "button",
2184
2185
  {
2185
2186
  onClick: handleApplyChanges,
@@ -2192,6 +2193,7 @@ function RectPanel({
2192
2193
  const DEFAULT_RECT_PROPS = {
2193
2194
  cornerRadius: 0,
2194
2195
  fillColor: "#3b82f6",
2196
+ // UI uses 0–100%; element opacity is 0–1
2195
2197
  opacity: 100,
2196
2198
  strokeColor: "#000000",
2197
2199
  lineWidth: 0
@@ -2206,25 +2208,56 @@ const useRectPanel = ({
2206
2208
  const [opacity, setOpacity] = useState(DEFAULT_RECT_PROPS.opacity);
2207
2209
  const [strokeColor, setStrokeColor] = useState(DEFAULT_RECT_PROPS.strokeColor);
2208
2210
  const [lineWidth, setLineWidth] = useState(DEFAULT_RECT_PROPS.lineWidth);
2211
+ const applyLiveChangesToExistingRect = (overrides = {}) => {
2212
+ if (!(selectedElement instanceof RectElement)) {
2213
+ return;
2214
+ }
2215
+ const rectElement = selectedElement;
2216
+ const nextCornerRadius = overrides.cornerRadius ?? cornerRadius;
2217
+ const nextFillColor = overrides.fillColor ?? fillColor;
2218
+ const nextOpacityPercent = overrides.opacity ?? opacity;
2219
+ const nextStrokeColor = overrides.strokeColor ?? strokeColor;
2220
+ const nextLineWidth = overrides.lineWidth ?? lineWidth;
2221
+ rectElement.setCornerRadius(nextCornerRadius);
2222
+ rectElement.setFill(nextFillColor);
2223
+ rectElement.setOpacity(nextOpacityPercent / 100);
2224
+ rectElement.setStrokeColor(nextStrokeColor);
2225
+ rectElement.setLineWidth(nextLineWidth);
2226
+ updateElement == null ? void 0 : updateElement(rectElement);
2227
+ };
2228
+ const handleCornerRadiusChange = (radius) => {
2229
+ setCornerRadius(radius);
2230
+ applyLiveChangesToExistingRect({ cornerRadius: radius });
2231
+ };
2232
+ const handleFillColorChange = (color) => {
2233
+ setFillColor(color);
2234
+ applyLiveChangesToExistingRect({ fillColor: color });
2235
+ };
2236
+ const handleOpacityChange = (nextOpacity) => {
2237
+ setOpacity(nextOpacity);
2238
+ applyLiveChangesToExistingRect({ opacity: nextOpacity });
2239
+ };
2240
+ const handleStrokeColorChange = (color) => {
2241
+ setStrokeColor(color);
2242
+ applyLiveChangesToExistingRect({ strokeColor: color });
2243
+ };
2244
+ const handleLineWidthChange = (width) => {
2245
+ setLineWidth(width);
2246
+ applyLiveChangesToExistingRect({ lineWidth: width });
2247
+ };
2209
2248
  const handleApplyChanges = () => {
2210
- let rectElement;
2211
2249
  if (selectedElement instanceof RectElement) {
2212
- rectElement = selectedElement;
2213
- rectElement.setCornerRadius(cornerRadius);
2214
- rectElement.setOpacity(opacity);
2215
- rectElement.setStrokeColor(strokeColor);
2216
- rectElement.setLineWidth(lineWidth);
2217
- updateElement == null ? void 0 : updateElement(rectElement);
2218
- } else {
2219
- rectElement = new RectElement(fillColor, { width: 200, height: 200 }).setCornerRadius(cornerRadius).setOpacity(opacity).setStrokeColor(strokeColor).setLineWidth(lineWidth);
2220
- addElement == null ? void 0 : addElement(rectElement);
2250
+ return;
2221
2251
  }
2252
+ const rectElement = new RectElement(fillColor, { width: 200, height: 200 }).setCornerRadius(cornerRadius).setOpacity(opacity / 100).setStrokeColor(strokeColor).setLineWidth(lineWidth);
2253
+ addElement == null ? void 0 : addElement(rectElement);
2222
2254
  };
2223
2255
  useEffect(() => {
2224
2256
  if (selectedElement instanceof RectElement) {
2225
2257
  setCornerRadius(selectedElement.getCornerRadius() ?? DEFAULT_RECT_PROPS.cornerRadius);
2226
2258
  setFillColor(selectedElement.getFill() ?? DEFAULT_RECT_PROPS.fillColor);
2227
- setOpacity(selectedElement.getOpacity() ?? DEFAULT_RECT_PROPS.opacity);
2259
+ const elementOpacity = selectedElement.getOpacity();
2260
+ setOpacity(elementOpacity != null ? elementOpacity * 100 : DEFAULT_RECT_PROPS.opacity);
2228
2261
  setStrokeColor(selectedElement.getStrokeColor() ?? DEFAULT_RECT_PROPS.strokeColor);
2229
2262
  setLineWidth(selectedElement.getLineWidth() ?? DEFAULT_RECT_PROPS.lineWidth);
2230
2263
  }
@@ -2236,11 +2269,11 @@ const useRectPanel = ({
2236
2269
  strokeColor,
2237
2270
  lineWidth,
2238
2271
  operation: selectedElement instanceof RectElement ? "Apply Changes" : "Add Rectangle",
2239
- setCornerRadius,
2240
- setFillColor,
2241
- setOpacity,
2242
- setStrokeColor,
2243
- setLineWidth,
2272
+ setCornerRadius: handleCornerRadiusChange,
2273
+ setFillColor: handleFillColorChange,
2274
+ setOpacity: handleOpacityChange,
2275
+ setStrokeColor: handleStrokeColorChange,
2276
+ setLineWidth: handleLineWidthChange,
2244
2277
  handleApplyChanges
2245
2278
  };
2246
2279
  };
@@ -2255,13 +2288,11 @@ function RectPanelContainer(props) {
2255
2288
  function CirclePanel({
2256
2289
  radius,
2257
2290
  fillColor,
2258
- opacity,
2259
2291
  strokeColor,
2260
2292
  lineWidth,
2261
2293
  operation,
2262
2294
  setRadius,
2263
2295
  setFillColor,
2264
- setOpacity,
2265
2296
  setStrokeColor,
2266
2297
  setLineWidth,
2267
2298
  handleApplyChanges
@@ -2311,26 +2342,6 @@ function CirclePanel({
2311
2342
  )
2312
2343
  ] })
2313
2344
  ] }),
2314
- /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
2315
- /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Opacity" }),
2316
- /* @__PURE__ */ jsxs("div", { className: "slider-container", children: [
2317
- /* @__PURE__ */ jsx(
2318
- "input",
2319
- {
2320
- type: "range",
2321
- min: "0",
2322
- max: "100",
2323
- value: opacity,
2324
- onChange: (e) => setOpacity(Number(e.target.value)),
2325
- className: "slider-purple"
2326
- }
2327
- ),
2328
- /* @__PURE__ */ jsxs("span", { className: "slider-value", children: [
2329
- opacity,
2330
- "%"
2331
- ] })
2332
- ] })
2333
- ] }),
2334
2345
  /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
2335
2346
  /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Stroke Color" }),
2336
2347
  /* @__PURE__ */ jsxs("div", { className: "color-inputs", children: [
@@ -2374,12 +2385,13 @@ function CirclePanel({
2374
2385
  ] })
2375
2386
  ] })
2376
2387
  ] }),
2377
- /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx("button", { onClick: handleApplyChanges, className: "btn-primary w-full", children: operation }) })
2388
+ operation !== "Apply Changes" && /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx("button", { onClick: handleApplyChanges, className: "btn-primary w-full", children: operation }) })
2378
2389
  ] });
2379
2390
  }
2380
2391
  const DEFAULT_CIRCLE_PROPS = {
2381
2392
  radius: 50,
2382
2393
  fillColor: "#3b82f6",
2394
+ // UI uses 0–100%; element opacity is 0–1
2383
2395
  opacity: 100,
2384
2396
  strokeColor: "#000000",
2385
2397
  lineWidth: 0
@@ -2394,26 +2406,56 @@ const useCirclePanel = ({
2394
2406
  const [opacity, setOpacity] = useState(DEFAULT_CIRCLE_PROPS.opacity);
2395
2407
  const [strokeColor, setStrokeColor] = useState(DEFAULT_CIRCLE_PROPS.strokeColor);
2396
2408
  const [lineWidth, setLineWidth] = useState(DEFAULT_CIRCLE_PROPS.lineWidth);
2409
+ const applyLiveChangesToExistingCircle = (overrides = {}) => {
2410
+ if (!(selectedElement instanceof CircleElement)) {
2411
+ return;
2412
+ }
2413
+ const circleElement = selectedElement;
2414
+ const nextRadius = overrides.radius ?? radius;
2415
+ const nextFillColor = overrides.fillColor ?? fillColor;
2416
+ const nextOpacityPercent = overrides.opacity ?? opacity;
2417
+ const nextStrokeColor = overrides.strokeColor ?? strokeColor;
2418
+ const nextLineWidth = overrides.lineWidth ?? lineWidth;
2419
+ circleElement.setRadius(nextRadius);
2420
+ circleElement.setFill(nextFillColor);
2421
+ circleElement.setOpacity(nextOpacityPercent / 100);
2422
+ circleElement.setStrokeColor(nextStrokeColor);
2423
+ circleElement.setLineWidth(nextLineWidth);
2424
+ updateElement == null ? void 0 : updateElement(circleElement);
2425
+ };
2426
+ const handleRadiusChange = (nextRadius) => {
2427
+ setRadius(nextRadius);
2428
+ applyLiveChangesToExistingCircle({ radius: nextRadius });
2429
+ };
2430
+ const handleFillColorChange = (color) => {
2431
+ setFillColor(color);
2432
+ applyLiveChangesToExistingCircle({ fillColor: color });
2433
+ };
2434
+ const handleOpacityChange = (nextOpacity) => {
2435
+ setOpacity(nextOpacity);
2436
+ applyLiveChangesToExistingCircle({ opacity: nextOpacity });
2437
+ };
2438
+ const handleStrokeColorChange = (color) => {
2439
+ setStrokeColor(color);
2440
+ applyLiveChangesToExistingCircle({ strokeColor: color });
2441
+ };
2442
+ const handleLineWidthChange = (width) => {
2443
+ setLineWidth(width);
2444
+ applyLiveChangesToExistingCircle({ lineWidth: width });
2445
+ };
2397
2446
  const handleApplyChanges = () => {
2398
- let circleElement;
2399
2447
  if (selectedElement instanceof CircleElement) {
2400
- circleElement = selectedElement;
2401
- circleElement.setRadius(radius);
2402
- circleElement.setFill(fillColor);
2403
- circleElement.setOpacity(opacity);
2404
- circleElement.setStrokeColor(strokeColor);
2405
- circleElement.setLineWidth(lineWidth);
2406
- updateElement == null ? void 0 : updateElement(circleElement);
2407
- } else {
2408
- circleElement = new CircleElement(fillColor, radius).setOpacity(opacity).setStrokeColor(strokeColor).setLineWidth(lineWidth);
2409
- addElement == null ? void 0 : addElement(circleElement);
2448
+ return;
2410
2449
  }
2450
+ const circleElement = new CircleElement(fillColor, radius).setOpacity(opacity / 100).setStrokeColor(strokeColor).setLineWidth(lineWidth);
2451
+ addElement == null ? void 0 : addElement(circleElement);
2411
2452
  };
2412
2453
  useEffect(() => {
2413
2454
  if (selectedElement instanceof CircleElement) {
2414
2455
  setRadius(selectedElement.getRadius() ?? DEFAULT_CIRCLE_PROPS.radius);
2415
2456
  setFillColor(selectedElement.getFill() ?? DEFAULT_CIRCLE_PROPS.fillColor);
2416
- setOpacity(selectedElement.getOpacity() ?? DEFAULT_CIRCLE_PROPS.opacity);
2457
+ const elementOpacity = selectedElement.getOpacity();
2458
+ setOpacity(elementOpacity != null ? elementOpacity * 100 : DEFAULT_CIRCLE_PROPS.opacity);
2417
2459
  setStrokeColor(selectedElement.getStrokeColor() ?? DEFAULT_CIRCLE_PROPS.strokeColor);
2418
2460
  setLineWidth(selectedElement.getLineWidth() ?? DEFAULT_CIRCLE_PROPS.lineWidth);
2419
2461
  }
@@ -2425,11 +2467,11 @@ const useCirclePanel = ({
2425
2467
  strokeColor,
2426
2468
  lineWidth,
2427
2469
  operation: selectedElement instanceof CircleElement ? "Apply Changes" : "Add Circle",
2428
- setRadius,
2429
- setFillColor,
2430
- setOpacity,
2431
- setStrokeColor,
2432
- setLineWidth,
2470
+ setRadius: handleRadiusChange,
2471
+ setFillColor: handleFillColorChange,
2472
+ setOpacity: handleOpacityChange,
2473
+ setStrokeColor: handleStrokeColorChange,
2474
+ setLineWidth: handleLineWidthChange,
2433
2475
  handleApplyChanges
2434
2476
  };
2435
2477
  };
@@ -2441,16 +2483,16 @@ function CirclePanelContainer(props) {
2441
2483
  });
2442
2484
  return /* @__PURE__ */ jsx(CirclePanel, { ...circlePanelProps });
2443
2485
  }
2444
- function SubtitlesPanel({
2445
- subtitles,
2446
- addSubtitle,
2447
- splitSubtitle,
2448
- deleteSubtitle,
2449
- updateSubtitle
2486
+ function CaptionsPanel({
2487
+ captions,
2488
+ addCaption,
2489
+ splitCaption,
2490
+ deleteCaption,
2491
+ updateCaption
2450
2492
  }) {
2451
2493
  return /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
2452
- /* @__PURE__ */ jsx("h3", { className: "panel-title", children: "Subtitles" }),
2453
- subtitles.map((subtitle, i) => /* @__PURE__ */ jsxs(
2494
+ /* @__PURE__ */ jsx("h3", { className: "panel-title", children: "Captions" }),
2495
+ captions.map((caption, i) => /* @__PURE__ */ jsxs(
2454
2496
  "div",
2455
2497
  {
2456
2498
  className: "panel-section gap-2",
@@ -2459,9 +2501,9 @@ function SubtitlesPanel({
2459
2501
  "input",
2460
2502
  {
2461
2503
  type: "text",
2462
- placeholder: "Enter subtitle text",
2463
- value: subtitle.t,
2464
- onChange: (e) => updateSubtitle(i, { ...subtitle, t: e.target.value }),
2504
+ placeholder: "Enter caption text",
2505
+ value: caption.t,
2506
+ onChange: (e) => updateCaption(i, { ...caption, t: e.target.value }),
2465
2507
  className: "input-dark"
2466
2508
  }
2467
2509
  ) }),
@@ -2469,18 +2511,18 @@ function SubtitlesPanel({
2469
2511
  /* @__PURE__ */ jsx(
2470
2512
  "button",
2471
2513
  {
2472
- onClick: () => splitSubtitle(i),
2514
+ onClick: () => splitCaption(i),
2473
2515
  className: "btn-ghost",
2474
- title: "Split subtitle",
2516
+ title: "Split caption",
2475
2517
  children: /* @__PURE__ */ jsx(Scissors, { className: "icon-sm" })
2476
2518
  }
2477
2519
  ),
2478
2520
  /* @__PURE__ */ jsx(
2479
2521
  "button",
2480
2522
  {
2481
- onClick: () => deleteSubtitle(i),
2523
+ onClick: () => deleteCaption(i),
2482
2524
  className: "btn-ghost",
2483
- title: "Delete subtitle",
2525
+ title: "Delete caption",
2484
2526
  children: /* @__PURE__ */ jsx(Trash2, { className: "icon-sm", color: "var(--color-red-500)" })
2485
2527
  }
2486
2528
  )
@@ -2489,7 +2531,7 @@ function SubtitlesPanel({
2489
2531
  },
2490
2532
  i
2491
2533
  )),
2492
- /* @__PURE__ */ jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsx("button", { onClick: addSubtitle, className: "btn-primary w-full", title: "Add subtitle", children: "Add" }) })
2534
+ /* @__PURE__ */ jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsx("button", { onClick: addCaption, className: "btn-primary w-full", title: "Add caption", children: "Add" }) })
2493
2535
  ] });
2494
2536
  }
2495
2537
  const CAPTION_PROPS = {
@@ -2544,16 +2586,16 @@ const CAPTION_PROPS = {
2544
2586
  shadowBlur: 5
2545
2587
  }
2546
2588
  };
2547
- const useSubtitlesPanel = () => {
2548
- const [subtitles, setSubtitles] = useState([]);
2549
- const subtitlesTrack = useRef(null);
2589
+ const useCaptionsPanel = () => {
2590
+ const [captions, setCaptions] = useState([]);
2591
+ const captionsTrack = useRef(null);
2550
2592
  const { editor } = useTimelineContext();
2551
- const fetchSubtitles = async () => {
2552
- const editorSubtitlesTrack = editor.getSubtiltesTrack();
2553
- if (editorSubtitlesTrack) {
2554
- subtitlesTrack.current = editorSubtitlesTrack;
2555
- setSubtitles(
2556
- editorSubtitlesTrack.getElements().map((element) => ({
2593
+ const fetchCaptions = async () => {
2594
+ const editorCaptionsTrack = editor.getCaptionsTrack();
2595
+ if (editorCaptionsTrack) {
2596
+ captionsTrack.current = editorCaptionsTrack;
2597
+ setCaptions(
2598
+ editorCaptionsTrack.getElements().map((element) => ({
2557
2599
  s: element.getStart(),
2558
2600
  e: element.getEnd(),
2559
2601
  t: element.getText()
@@ -2562,12 +2604,12 @@ const useSubtitlesPanel = () => {
2562
2604
  }
2563
2605
  };
2564
2606
  useEffect(() => {
2565
- fetchSubtitles();
2607
+ fetchCaptions();
2566
2608
  }, []);
2567
- const checkSubtitlesTrack = () => {
2609
+ const checkCaptionsTrack = () => {
2568
2610
  var _a;
2569
- if (!subtitlesTrack.current) {
2570
- subtitlesTrack.current = editor.addTrack("Subtitles", "caption");
2611
+ if (!captionsTrack.current) {
2612
+ captionsTrack.current = editor.addTrack("Caption", "caption");
2571
2613
  const props = {
2572
2614
  capStyle: CAPTION_STYLE.WORD_BG_HIGHLIGHT,
2573
2615
  ...CAPTION_PROPS[CAPTION_STYLE.WORD_BG_HIGHLIGHT],
@@ -2575,61 +2617,61 @@ const useSubtitlesPanel = () => {
2575
2617
  y: 200,
2576
2618
  applyToAll: true
2577
2619
  };
2578
- (_a = subtitlesTrack.current) == null ? void 0 : _a.setProps(props);
2620
+ (_a = captionsTrack.current) == null ? void 0 : _a.setProps(props);
2579
2621
  }
2580
2622
  };
2581
- const addSubtitle = () => {
2582
- const newSubtitle = { s: 0, e: 0, t: "New Subtitle" };
2583
- if (subtitles.length > 0) {
2584
- newSubtitle.s = subtitles[subtitles.length - 1].e;
2585
- }
2586
- newSubtitle.e = newSubtitle.s + 1;
2587
- setSubtitles([...subtitles, newSubtitle]);
2588
- checkSubtitlesTrack();
2623
+ const addCaption = () => {
2624
+ const newCaption = { s: 0, e: 0, t: "New Caption" };
2625
+ if (captions.length > 0) {
2626
+ newCaption.s = captions[captions.length - 1].e;
2627
+ }
2628
+ newCaption.e = newCaption.s + 1;
2629
+ setCaptions([...captions, newCaption]);
2630
+ checkCaptionsTrack();
2589
2631
  const captionElement = new CaptionElement(
2590
- newSubtitle.t,
2591
- newSubtitle.s,
2592
- newSubtitle.e
2632
+ newCaption.t,
2633
+ newCaption.s,
2634
+ newCaption.e
2593
2635
  );
2594
- editor.addElementToTrack(subtitlesTrack.current, captionElement);
2636
+ editor.addElementToTrack(captionsTrack.current, captionElement);
2595
2637
  };
2596
- const splitSubtitle = async (index) => {
2597
- if (subtitlesTrack.current) {
2598
- const element = subtitlesTrack.current.getElements()[index];
2638
+ const splitCaption = async (index) => {
2639
+ if (captionsTrack.current) {
2640
+ const element = captionsTrack.current.getElements()[index];
2599
2641
  const splitResult = await editor.splitElement(
2600
2642
  element,
2601
2643
  element.getStart() + element.getDuration() / 2
2602
2644
  );
2603
2645
  if (splitResult.success) {
2604
- fetchSubtitles();
2646
+ fetchCaptions();
2605
2647
  }
2606
2648
  }
2607
2649
  };
2608
- const deleteSubtitle = (index) => {
2609
- setSubtitles(subtitles.filter((_, i) => i !== index));
2610
- if (subtitlesTrack.current) {
2611
- editor.removeElement(subtitlesTrack.current.getElements()[index]);
2650
+ const deleteCaption = (index) => {
2651
+ setCaptions(captions.filter((_, i) => i !== index));
2652
+ if (captionsTrack.current) {
2653
+ editor.removeElement(captionsTrack.current.getElements()[index]);
2612
2654
  }
2613
2655
  };
2614
- const updateSubtitle = (index, subtitle) => {
2615
- setSubtitles(subtitles.map((sub, i) => i === index ? subtitle : sub));
2616
- if (subtitlesTrack.current) {
2617
- const element = subtitlesTrack.current.getElements()[index];
2618
- element.setText(subtitle.t);
2656
+ const updateCaption = (index, caption) => {
2657
+ setCaptions(captions.map((sub, i) => i === index ? caption : sub));
2658
+ if (captionsTrack.current) {
2659
+ const element = captionsTrack.current.getElements()[index];
2660
+ element.setText(caption.t);
2619
2661
  editor.updateElement(element);
2620
2662
  }
2621
2663
  };
2622
2664
  return {
2623
- subtitles,
2624
- addSubtitle,
2625
- splitSubtitle,
2626
- deleteSubtitle,
2627
- updateSubtitle
2665
+ captions,
2666
+ addCaption,
2667
+ splitCaption,
2668
+ deleteCaption,
2669
+ updateCaption
2628
2670
  };
2629
2671
  };
2630
- function SubtitlesPanelContainer() {
2631
- const subtitlesPanelProps = useSubtitlesPanel();
2632
- return /* @__PURE__ */ jsx(SubtitlesPanel, { ...subtitlesPanelProps });
2672
+ function CaptionsPanelContainer() {
2673
+ const captionsPanelProps = useCaptionsPanel();
2674
+ return /* @__PURE__ */ jsx(CaptionsPanel, { ...captionsPanelProps });
2633
2675
  }
2634
2676
  const ElementPanelContainer = ({
2635
2677
  selectedTool,
@@ -2682,16 +2724,6 @@ const ElementPanelContainer = ({
2682
2724
  updateElement
2683
2725
  }
2684
2726
  );
2685
- case "icon":
2686
- return /* @__PURE__ */ jsx(
2687
- IconPanelContainer,
2688
- {
2689
- videoResolution,
2690
- selectedElement,
2691
- addElement: addNewElement,
2692
- updateElement
2693
- }
2694
- );
2695
2727
  case "rect":
2696
2728
  return /* @__PURE__ */ jsx(
2697
2729
  RectPanelContainer,
@@ -2712,8 +2744,8 @@ const ElementPanelContainer = ({
2712
2744
  updateElement
2713
2745
  }
2714
2746
  );
2715
- case "subtitle":
2716
- return /* @__PURE__ */ jsx(SubtitlesPanelContainer, {});
2747
+ case "caption":
2748
+ return /* @__PURE__ */ jsx(CaptionsPanelContainer, {});
2717
2749
  default:
2718
2750
  return /* @__PURE__ */ jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxs("div", { className: "empty-state-content", children: [
2719
2751
  /* @__PURE__ */ jsx(WandSparkles, { className: "empty-state-icon" }),
@@ -2723,159 +2755,37 @@ const ElementPanelContainer = ({
2723
2755
  };
2724
2756
  return renderLibrary();
2725
2757
  };
2726
- const propsCategories = /* @__PURE__ */ new Map([
2727
- [
2728
- "element-props",
2729
- {
2730
- id: "element-props",
2731
- name: "Properties",
2732
- icon: "Settings",
2733
- description: "Element Properties"
2734
- }
2735
- ],
2736
- [
2737
- "animations",
2738
- {
2739
- id: "animations",
2740
- name: "Animations",
2741
- icon: "Zap",
2742
- description: "Animations"
2743
- }
2744
- ],
2745
- [
2746
- "text-effects",
2747
- {
2748
- id: "text-effects",
2749
- name: "Text Effects",
2750
- icon: "SparklesIcon",
2751
- description: "Text Effects"
2752
- }
2753
- ],
2754
- [
2755
- "color-effects",
2756
- {
2757
- id: "color-effects",
2758
- name: "Color Effects",
2759
- icon: "Image",
2760
- description: "Color Effects"
2761
- }
2762
- ],
2763
- [
2764
- "playback-props",
2765
- {
2766
- id: "playback-props",
2767
- name: "Playback Props",
2768
- icon: "Music",
2769
- description: "Playback Properties"
2770
- }
2771
- ],
2772
- [
2773
- "subtitle-style",
2774
- {
2775
- id: "subtitle-style",
2776
- name: "Subtitle Style",
2777
- icon: "MessageSquare",
2778
- description: "Subtitle Style"
2779
- }
2780
- ],
2781
- [
2782
- "generate-subtitles",
2783
- {
2784
- id: "generate-subtitles",
2785
- name: "Generate Subtitles",
2786
- icon: "Subtitles",
2787
- description: "Generate Subtitles"
2788
- }
2789
- ]
2790
- ]);
2791
- const getIcon = (iconName) => {
2792
- switch (iconName) {
2793
- case "Type":
2794
- return Type;
2795
- case "Infinity":
2796
- return Infinity;
2797
- case "Image":
2798
- return Image;
2799
- case "Music":
2800
- return Music;
2801
- case "Subtitles":
2802
- return Captions;
2803
- case "MessageSquare":
2804
- return MessageSquare;
2805
- case "Settings":
2806
- return Settings;
2807
- case "SparklesIcon":
2808
- return Sparkles;
2809
- case "Zap":
2810
- return Zap;
2811
- default:
2812
- return Plus;
2813
- }
2814
- };
2815
- function PropsToolbar({
2816
- selectedElement,
2817
- selectedProp,
2818
- setSelectedProp
2819
- }) {
2820
- const availableSections = useMemo(() => {
2821
- const sections = [];
2822
- if (selectedElement instanceof TextElement) {
2823
- sections.push(propsCategories.get("element-props"));
2824
- sections.push(propsCategories.get("animations"));
2825
- sections.push(propsCategories.get("text-effects"));
2826
- } else if (selectedElement instanceof ImageElement) {
2827
- sections.push(propsCategories.get("element-props"));
2828
- sections.push(propsCategories.get("animations"));
2829
- sections.push(propsCategories.get("color-effects"));
2830
- } else if (selectedElement instanceof VideoElement) {
2831
- sections.push(propsCategories.get("element-props"));
2832
- sections.push(propsCategories.get("animations"));
2833
- sections.push(propsCategories.get("color-effects"));
2834
- sections.push(propsCategories.get("playback-props"));
2835
- sections.push(propsCategories.get("generate-subtitles"));
2836
- } else if (selectedElement instanceof AudioElement) {
2837
- sections.push(propsCategories.get("element-props"));
2838
- sections.push(propsCategories.get("playback-props"));
2839
- } else if (selectedElement instanceof CircleElement) {
2840
- sections.push(propsCategories.get("element-props"));
2841
- sections.push(propsCategories.get("animations"));
2842
- } else if (selectedElement instanceof RectElement) {
2843
- sections.push(propsCategories.get("element-props"));
2844
- sections.push(propsCategories.get("animations"));
2845
- } else if (selectedElement instanceof IconElement) {
2846
- sections.push(propsCategories.get("element-props"));
2847
- sections.push(propsCategories.get("animations"));
2848
- } else if (selectedElement instanceof CaptionElement) {
2849
- sections.push(propsCategories.get("element-props"));
2850
- sections.push(propsCategories.get("animations"));
2851
- sections.push(propsCategories.get("subtitle-style"));
2852
- }
2853
- return sections;
2854
- }, [selectedElement]);
2855
- useEffect(() => {
2856
- if (availableSections == null ? void 0 : availableSections.length) {
2857
- if (availableSections.map((section) => section.id).indexOf(selectedProp) === -1) {
2858
- setSelectedProp(availableSections[0].id);
2859
- }
2860
- }
2861
- }, [availableSections]);
2862
- return /* @__PURE__ */ jsx("div", { className: "sidebar", children: availableSections.map((tool) => {
2863
- const Icon2 = getIcon(tool.icon);
2864
- const isSelected = selectedProp === tool.id;
2865
- return /* @__PURE__ */ jsxs(
2758
+ function PropertyRow({ label, children, secondary }) {
2759
+ return /* @__PURE__ */ jsxs("div", { className: "property-row", children: [
2760
+ /* @__PURE__ */ jsx("div", { className: "property-row-label", children: /* @__PURE__ */ jsx("span", { className: "property-label", children: label }) }),
2761
+ /* @__PURE__ */ jsx("div", { className: "property-row-control", children }),
2762
+ secondary && /* @__PURE__ */ jsx("div", { className: "property-row-secondary", children: secondary })
2763
+ ] });
2764
+ }
2765
+ function AccordionItem({ title, icon, children, isOpen, onToggle }) {
2766
+ return /* @__PURE__ */ jsxs("div", { className: "accordion-item", children: [
2767
+ /* @__PURE__ */ jsxs(
2866
2768
  "div",
2867
2769
  {
2868
- onClick: () => setSelectedProp(tool.id),
2869
- className: `toolbar-btn ${isSelected ? "active" : ""}`,
2870
- title: `${tool.name}${tool.shortcut ? ` (${tool.shortcut})` : ""}`,
2770
+ onClick: onToggle,
2771
+ className: "accordion-header",
2871
2772
  children: [
2872
- /* @__PURE__ */ jsx(Icon2, { className: "icon-sm" }),
2873
- /* @__PURE__ */ jsx("span", { className: "props-toolbar-label", children: tool.name })
2773
+ /* @__PURE__ */ jsxs("div", { className: "flex-container", children: [
2774
+ /* @__PURE__ */ jsx("div", { className: "accent-purple", children: icon }),
2775
+ /* @__PURE__ */ jsx("span", { className: "property-title", children: title })
2776
+ ] }),
2777
+ isOpen ? /* @__PURE__ */ jsx(ChevronDown, { className: "icon-sm accent-purple" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "icon-sm accent-purple" })
2874
2778
  ]
2875
- },
2876
- tool.id
2877
- );
2878
- }) });
2779
+ }
2780
+ ),
2781
+ /* @__PURE__ */ jsx(
2782
+ "div",
2783
+ {
2784
+ className: `accordion-content ${isOpen ? "expanded" : ""}`,
2785
+ children: /* @__PURE__ */ jsx("div", { className: "accordion-panel", children })
2786
+ }
2787
+ )
2788
+ ] });
2879
2789
  }
2880
2790
  function ElementProps({ selectedElement, updateElement }) {
2881
2791
  const opacity = (selectedElement == null ? void 0 : selectedElement.getOpacity()) || 1;
@@ -2899,77 +2809,134 @@ function ElementProps({ selectedElement, updateElement }) {
2899
2809
  updateElement == null ? void 0 : updateElement(selectedElement);
2900
2810
  }
2901
2811
  };
2812
+ const handleDimensionsChange = (width, height) => {
2813
+ if (!selectedElement) return;
2814
+ if (selectedElement instanceof RectElement) {
2815
+ const size = selectedElement.getSize();
2816
+ selectedElement.setSize({ width: width ?? size.width, height: height ?? size.height });
2817
+ updateElement == null ? void 0 : updateElement(selectedElement);
2818
+ } else if (selectedElement instanceof CircleElement) {
2819
+ const dims = {
2820
+ width: selectedElement.getRadius() * 2,
2821
+ height: selectedElement.getRadius() * 2
2822
+ };
2823
+ const newDiameter = width !== void 0 && width !== dims.width ? width : height ?? dims.height;
2824
+ selectedElement.setRadius(newDiameter / 2);
2825
+ updateElement == null ? void 0 : updateElement(selectedElement);
2826
+ }
2827
+ };
2828
+ const hasShapeDimensions = selectedElement instanceof RectElement || selectedElement instanceof CircleElement;
2829
+ let dimensions = null;
2830
+ if (selectedElement instanceof RectElement) {
2831
+ dimensions = selectedElement.getSize();
2832
+ } else if (selectedElement instanceof CircleElement) {
2833
+ const r = selectedElement.getRadius();
2834
+ dimensions = { width: r * 2, height: r * 2 };
2835
+ }
2836
+ const [isTransformOpen, setIsTransformOpen] = useState(false);
2902
2837
  return /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
2903
- /* @__PURE__ */ jsx("div", { className: "panel-title", children: "All Properties" }),
2904
- /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
2905
- /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Position" }),
2906
- /* @__PURE__ */ jsxs("div", { className: "flex-container", children: [
2907
- /* @__PURE__ */ jsxs("div", { children: [
2908
- /* @__PURE__ */ jsx("label", { className: "label-small", children: "X" }),
2909
- /* @__PURE__ */ jsx(
2910
- "input",
2838
+ /* @__PURE__ */ jsx("div", { className: "panel-title", children: "Properties" }),
2839
+ /* @__PURE__ */ jsx(
2840
+ AccordionItem,
2841
+ {
2842
+ title: "Transform",
2843
+ icon: /* @__PURE__ */ jsx(Ruler, { className: "icon-sm" }),
2844
+ isOpen: isTransformOpen,
2845
+ onToggle: () => setIsTransformOpen((open) => !open),
2846
+ children: /* @__PURE__ */ jsxs("div", { className: "properties-group", children: [
2847
+ /* @__PURE__ */ jsxs("div", { className: "property-section", children: [
2848
+ /* @__PURE__ */ jsx(PropertyRow, { label: "Position X", children: /* @__PURE__ */ jsx(
2849
+ "input",
2850
+ {
2851
+ type: "number",
2852
+ value: position.x ?? 0,
2853
+ onChange: (e) => handlePositionChange({ x: Number(e.target.value) }),
2854
+ className: "input-dark"
2855
+ }
2856
+ ) }),
2857
+ /* @__PURE__ */ jsx(PropertyRow, { label: "Position Y", children: /* @__PURE__ */ jsx(
2858
+ "input",
2859
+ {
2860
+ type: "number",
2861
+ value: position.y ?? 0,
2862
+ onChange: (e) => handlePositionChange({ y: Number(e.target.value) }),
2863
+ className: "input-dark"
2864
+ }
2865
+ ) })
2866
+ ] }),
2867
+ hasShapeDimensions && dimensions && /* @__PURE__ */ jsxs("div", { className: "property-section", children: [
2868
+ /* @__PURE__ */ jsx(PropertyRow, { label: "Width", children: /* @__PURE__ */ jsx(
2869
+ "input",
2870
+ {
2871
+ type: "number",
2872
+ min: 1,
2873
+ value: Math.round(dimensions.width),
2874
+ onChange: (e) => handleDimensionsChange(
2875
+ Number(e.target.value),
2876
+ dimensions.height
2877
+ ),
2878
+ className: "input-dark"
2879
+ }
2880
+ ) }),
2881
+ /* @__PURE__ */ jsx(PropertyRow, { label: "Height", children: /* @__PURE__ */ jsx(
2882
+ "input",
2883
+ {
2884
+ type: "number",
2885
+ min: 1,
2886
+ value: Math.round(dimensions.height),
2887
+ onChange: (e) => handleDimensionsChange(
2888
+ dimensions.width,
2889
+ Number(e.target.value)
2890
+ ),
2891
+ className: "input-dark"
2892
+ }
2893
+ ) })
2894
+ ] }),
2895
+ /* @__PURE__ */ jsx("div", { className: "property-section", children: /* @__PURE__ */ jsx(
2896
+ PropertyRow,
2911
2897
  {
2912
- type: "number",
2913
- value: position.x ?? 0,
2914
- onChange: (e) => handlePositionChange({ x: Number(e.target.value) }),
2915
- className: "input-dark"
2898
+ label: "Opacity",
2899
+ secondary: /* @__PURE__ */ jsxs("span", { children: [
2900
+ Math.round((opacity ?? 1) * 100),
2901
+ "%"
2902
+ ] }),
2903
+ children: /* @__PURE__ */ jsx(
2904
+ "input",
2905
+ {
2906
+ type: "range",
2907
+ min: "0",
2908
+ max: "100",
2909
+ value: (opacity ?? 1) * 100,
2910
+ onChange: (e) => handleOpacityChange(Number(e.target.value) / 100),
2911
+ className: "slider-purple"
2912
+ }
2913
+ )
2916
2914
  }
2917
- )
2918
- ] }),
2919
- /* @__PURE__ */ jsxs("div", { children: [
2920
- /* @__PURE__ */ jsx("label", { className: "label-small", children: "Y" }),
2921
- /* @__PURE__ */ jsx(
2922
- "input",
2915
+ ) }),
2916
+ /* @__PURE__ */ jsx("div", { className: "property-section", children: /* @__PURE__ */ jsx(
2917
+ PropertyRow,
2923
2918
  {
2924
- type: "number",
2925
- value: position.y ?? 0,
2926
- onChange: (e) => handlePositionChange({ y: Number(e.target.value) }),
2927
- className: "input-dark"
2919
+ label: "Rotation",
2920
+ secondary: /* @__PURE__ */ jsxs("span", { children: [
2921
+ Math.round(rotation ?? 0),
2922
+ "°"
2923
+ ] }),
2924
+ children: /* @__PURE__ */ jsx(
2925
+ "input",
2926
+ {
2927
+ type: "range",
2928
+ min: "0",
2929
+ max: "360",
2930
+ value: rotation ?? 0,
2931
+ onChange: (e) => handleRotationChange(Number(e.target.value)),
2932
+ className: "slider-purple"
2933
+ }
2934
+ )
2928
2935
  }
2929
- )
2930
- ] })
2931
- ] })
2932
- ] }),
2933
- /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
2934
- /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Opacity" }),
2935
- /* @__PURE__ */ jsxs("div", { className: "slider-container", children: [
2936
- /* @__PURE__ */ jsx(
2937
- "input",
2938
- {
2939
- type: "range",
2940
- min: "0",
2941
- max: "100",
2942
- value: (opacity ?? 1) * 100,
2943
- onChange: (e) => handleOpacityChange(Number(e.target.value) / 100),
2944
- className: "slider-purple"
2945
- }
2946
- ),
2947
- /* @__PURE__ */ jsxs("span", { className: "slider-value", children: [
2948
- Math.round((opacity ?? 1) * 100),
2949
- "%"
2950
- ] })
2951
- ] })
2952
- ] }),
2953
- /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
2954
- /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Rotation" }),
2955
- /* @__PURE__ */ jsxs("div", { className: "slider-container", children: [
2956
- /* @__PURE__ */ jsx(
2957
- "input",
2958
- {
2959
- type: "range",
2960
- min: "0",
2961
- max: "360",
2962
- value: rotation ?? 0,
2963
- onChange: (e) => handleRotationChange(Number(e.target.value)),
2964
- className: "slider-purple"
2965
- }
2966
- ),
2967
- /* @__PURE__ */ jsxs("span", { className: "slider-value", children: [
2968
- rotation ?? 0,
2969
- "°"
2936
+ ) })
2970
2937
  ] })
2971
- ] })
2972
- ] })
2938
+ }
2939
+ )
2973
2940
  ] });
2974
2941
  }
2975
2942
  function TextEffects({
@@ -3000,70 +2967,72 @@ function TextEffects({
3000
2967
  selectedElement.setTextEffect(effect);
3001
2968
  updateElement == null ? void 0 : updateElement(selectedElement);
3002
2969
  };
2970
+ const [isEffectsOpen, setIsEffectsOpen] = useState(false);
3003
2971
  return /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
3004
2972
  /* @__PURE__ */ jsx("div", { className: "panel-title", children: "Text Effects" }),
3005
- /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
3006
- /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Text Effect Type" }),
3007
- /* @__PURE__ */ jsxs(
3008
- "select",
3009
- {
3010
- value: (currentEffect == null ? void 0 : currentEffect.getName()) || "",
3011
- onChange: (e) => handleUpdateEffect({ name: e.target.value }),
3012
- className: "select-dark w-full",
3013
- children: [
3014
- /* @__PURE__ */ jsx("option", { value: "", children: "No Effect" }),
3015
- TEXT_EFFECTS.map((effect) => /* @__PURE__ */ jsx("option", { value: effect.name, children: effect.name.charAt(0).toUpperCase() + effect.name.slice(1) }, effect.name))
3016
- ]
3017
- }
3018
- )
3019
- ] }),
3020
- currentEffect && /* @__PURE__ */ jsxs(Fragment, { children: [
3021
- /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
3022
- /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Delay (seconds)" }),
3023
- /* @__PURE__ */ jsx(
3024
- "input",
3025
- {
3026
- type: "number",
3027
- min: "0",
3028
- max: "5",
3029
- step: "0.1",
3030
- value: currentEffect.getDelay() ?? 0,
3031
- onChange: (e) => handleUpdateEffect({ delay: Number(e.target.value) }),
3032
- className: "input-dark"
3033
- }
3034
- )
3035
- ] }),
3036
- /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
3037
- /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Duration (seconds)" }),
3038
- /* @__PURE__ */ jsx(
3039
- "input",
3040
- {
3041
- type: "number",
3042
- min: "0.1",
3043
- max: "10",
3044
- step: "0.1",
3045
- value: currentEffect.getDuration() ?? 1,
3046
- onChange: (e) => handleUpdateEffect({ duration: Number(e.target.value) }),
3047
- className: "input-dark"
3048
- }
3049
- )
3050
- ] }),
3051
- /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
3052
- /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Buffer Time (seconds)" }),
3053
- /* @__PURE__ */ jsx(
3054
- "input",
3055
- {
3056
- type: "number",
3057
- min: "0.05",
3058
- max: "1",
3059
- step: "0.05",
3060
- value: currentEffect.getBufferTime() ?? 0.1,
3061
- onChange: (e) => handleUpdateEffect({ bufferTime: Number(e.target.value) }),
3062
- className: "input-dark"
3063
- }
3064
- )
3065
- ] })
3066
- ] })
2973
+ /* @__PURE__ */ jsx(
2974
+ AccordionItem,
2975
+ {
2976
+ title: "Effects",
2977
+ icon: /* @__PURE__ */ jsx(Sparkles, { className: "icon-sm" }),
2978
+ isOpen: isEffectsOpen,
2979
+ onToggle: () => setIsEffectsOpen((open) => !open),
2980
+ children: /* @__PURE__ */ jsxs("div", { className: "properties-group", children: [
2981
+ /* @__PURE__ */ jsx("div", { className: "property-section", children: /* @__PURE__ */ jsx(PropertyRow, { label: "Preset", children: /* @__PURE__ */ jsxs(
2982
+ "select",
2983
+ {
2984
+ value: (currentEffect == null ? void 0 : currentEffect.getName()) || "",
2985
+ onChange: (e) => handleUpdateEffect({ name: e.target.value }),
2986
+ className: "select-dark w-full",
2987
+ children: [
2988
+ /* @__PURE__ */ jsx("option", { value: "", children: "No Effect" }),
2989
+ TEXT_EFFECTS.map((effect) => /* @__PURE__ */ jsx("option", { value: effect.name, children: effect.name.charAt(0).toUpperCase() + effect.name.slice(1) }, effect.name))
2990
+ ]
2991
+ }
2992
+ ) }) }),
2993
+ currentEffect && /* @__PURE__ */ jsxs(Fragment, { children: [
2994
+ /* @__PURE__ */ jsx("div", { className: "property-section", children: /* @__PURE__ */ jsx(PropertyRow, { label: "Delay (s)", children: /* @__PURE__ */ jsx(
2995
+ "input",
2996
+ {
2997
+ type: "number",
2998
+ min: "0",
2999
+ max: "5",
3000
+ step: "0.1",
3001
+ value: currentEffect.getDelay() ?? 0,
3002
+ onChange: (e) => handleUpdateEffect({ delay: Number(e.target.value) }),
3003
+ className: "input-dark"
3004
+ }
3005
+ ) }) }),
3006
+ /* @__PURE__ */ jsx("div", { className: "property-section", children: /* @__PURE__ */ jsx(PropertyRow, { label: "Duration (s)", children: /* @__PURE__ */ jsx(
3007
+ "input",
3008
+ {
3009
+ type: "number",
3010
+ min: "0.1",
3011
+ max: "10",
3012
+ step: "0.1",
3013
+ value: currentEffect.getDuration() ?? 1,
3014
+ onChange: (e) => handleUpdateEffect({ duration: Number(e.target.value) }),
3015
+ className: "input-dark"
3016
+ }
3017
+ ) }) }),
3018
+ /* @__PURE__ */ jsx("div", { className: "property-section", children: /* @__PURE__ */ jsx(PropertyRow, { label: "Buffer (s)", children: /* @__PURE__ */ jsx(
3019
+ "input",
3020
+ {
3021
+ type: "number",
3022
+ min: "0.05",
3023
+ max: "1",
3024
+ step: "0.05",
3025
+ value: currentEffect.getBufferTime() ?? 0.1,
3026
+ onChange: (e) => handleUpdateEffect({
3027
+ bufferTime: Number(e.target.value)
3028
+ }),
3029
+ className: "input-dark"
3030
+ }
3031
+ ) }) })
3032
+ ] })
3033
+ ] })
3034
+ }
3035
+ )
3067
3036
  ] });
3068
3037
  }
3069
3038
  function Animation({
@@ -3251,37 +3220,95 @@ function Animation({
3251
3220
  })() })
3252
3221
  ] });
3253
3222
  }
3223
+ const MIN_DB = -60;
3224
+ const MAX_DB = 6;
3225
+ function linearToDb(linear) {
3226
+ if (linear <= 0) return MIN_DB;
3227
+ const db = 20 * Math.log10(linear);
3228
+ return Math.max(MIN_DB, Math.min(MAX_DB, db));
3229
+ }
3230
+ function dbToLinear(db) {
3231
+ if (db <= MIN_DB) return 0;
3232
+ const linear = Math.pow(10, db / 20);
3233
+ return Math.min(linear, Math.pow(10, MAX_DB / 20));
3234
+ }
3235
+ const PLAYBACK_RATE_MIN = 0.25;
3236
+ const PLAYBACK_RATE_MAX = 2;
3237
+ const PLAYBACK_RATE_STEP = 0.25;
3254
3238
  function PlaybackPropsPanel({
3255
3239
  selectedElement,
3256
3240
  updateElement
3257
3241
  }) {
3258
3242
  const elementProps = (selectedElement == null ? void 0 : selectedElement.getProps()) || {};
3259
- const { volume } = elementProps;
3243
+ const volumeLinear = elementProps.volume ?? 1;
3244
+ const volumeDb = linearToDb(volumeLinear);
3245
+ const playbackRate = elementProps.playbackRate ?? 1;
3260
3246
  const handleUpdateElement = (props) => {
3261
3247
  if (selectedElement) {
3262
3248
  updateElement == null ? void 0 : updateElement(selectedElement == null ? void 0 : selectedElement.setProps({ ...elementProps, ...props }));
3263
3249
  }
3264
3250
  };
3251
+ const handleVolumeDbChange = (db) => {
3252
+ handleUpdateElement({ volume: dbToLinear(db) });
3253
+ };
3254
+ const handlePlaybackRateChange = (rate) => {
3255
+ handleUpdateElement({ playbackRate: rate });
3256
+ };
3257
+ const [isPlaybackOpen, setIsPlaybackOpen] = useState(false);
3265
3258
  return /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
3266
- /* @__PURE__ */ jsx("div", { className: "panel-title", children: "Playback Properties" }),
3267
- /* @__PURE__ */ jsxs("div", { className: "panel-section", children: [
3268
- /* @__PURE__ */ jsx("label", { className: "label-dark", children: "Volume" }),
3269
- /* @__PURE__ */ jsxs("div", { className: "slider-container", children: [
3270
- /* @__PURE__ */ jsx(
3271
- "input",
3272
- {
3273
- type: "range",
3274
- min: "0",
3275
- max: "3",
3276
- step: 0.1,
3277
- value: volume ?? 0,
3278
- onChange: (e) => handleUpdateElement({ volume: Number(e.target.value) }),
3279
- className: "slider-purple"
3280
- }
3281
- ),
3282
- /* @__PURE__ */ jsx("span", { className: "slider-value", children: volume ?? 0 })
3283
- ] })
3284
- ] })
3259
+ /* @__PURE__ */ jsx("div", { className: "panel-title", children: "Playback" }),
3260
+ /* @__PURE__ */ jsx(
3261
+ AccordionItem,
3262
+ {
3263
+ title: "Playback",
3264
+ icon: /* @__PURE__ */ jsx(Music2, { className: "icon-sm" }),
3265
+ isOpen: isPlaybackOpen,
3266
+ onToggle: () => setIsPlaybackOpen((open) => !open),
3267
+ children: /* @__PURE__ */ jsxs("div", { className: "properties-group", children: [
3268
+ /* @__PURE__ */ jsx("div", { className: "property-section", children: /* @__PURE__ */ jsx(
3269
+ PropertyRow,
3270
+ {
3271
+ label: "Playback rate",
3272
+ secondary: /* @__PURE__ */ jsxs("span", { children: [
3273
+ playbackRate,
3274
+ "×"
3275
+ ] }),
3276
+ children: /* @__PURE__ */ jsx(
3277
+ "input",
3278
+ {
3279
+ type: "range",
3280
+ min: PLAYBACK_RATE_MIN,
3281
+ max: PLAYBACK_RATE_MAX,
3282
+ step: PLAYBACK_RATE_STEP,
3283
+ value: playbackRate,
3284
+ onChange: (e) => handlePlaybackRateChange(Number(e.target.value)),
3285
+ className: "slider-purple"
3286
+ }
3287
+ )
3288
+ }
3289
+ ) }),
3290
+ /* @__PURE__ */ jsx("div", { className: "property-section", children: /* @__PURE__ */ jsx(
3291
+ PropertyRow,
3292
+ {
3293
+ label: "Volume",
3294
+ secondary: /* @__PURE__ */ jsx("span", { children: volumeDb <= MIN_DB ? "−∞" : `${Math.round(volumeDb)} dB` }),
3295
+ children: /* @__PURE__ */ jsx(
3296
+ "input",
3297
+ {
3298
+ type: "range",
3299
+ min: MIN_DB,
3300
+ max: MAX_DB,
3301
+ step: 1,
3302
+ value: volumeDb,
3303
+ onChange: (e) => handleVolumeDbChange(Number(e.target.value)),
3304
+ className: "slider-purple"
3305
+ }
3306
+ )
3307
+ }
3308
+ ) })
3309
+ ] })
3310
+ }
3311
+ )
3285
3312
  ] });
3286
3313
  }
3287
3314
  const hasAudio = async (src) => {
@@ -3370,11 +3397,11 @@ const saveAsFile = (content, type, name) => {
3370
3397
  a.click();
3371
3398
  URL.revokeObjectURL(url);
3372
3399
  };
3373
- function GenerateSubtitlesPanel({
3400
+ function GenerateCaptionsPanel({
3374
3401
  selectedElement,
3375
- addSubtitlesToTimeline,
3376
- onGenerateSubtitles,
3377
- getSubtitleStatus
3402
+ addCaptionsToTimeline,
3403
+ onGenerateCaptions,
3404
+ getCaptionstatus
3378
3405
  }) {
3379
3406
  const [containsAudio, setContainsAudio] = useState(null);
3380
3407
  const [isLoading, setIsLoading] = useState(true);
@@ -3397,7 +3424,7 @@ function GenerateSubtitlesPanel({
3397
3424
  }
3398
3425
  };
3399
3426
  const startPolling = async (reqId) => {
3400
- if (!getSubtitleStatus) {
3427
+ if (!getCaptionstatus) {
3401
3428
  return;
3402
3429
  }
3403
3430
  setPollingStatus("polling");
@@ -3405,12 +3432,12 @@ function GenerateSubtitlesPanel({
3405
3432
  setErrorMessage(null);
3406
3433
  const poll = async () => {
3407
3434
  try {
3408
- const response = await getSubtitleStatus(reqId);
3435
+ const response = await getCaptionstatus(reqId);
3409
3436
  if (response.status === "completed") {
3410
3437
  stopPolling();
3411
3438
  setPollingStatus("success");
3412
3439
  setIsGenerating(false);
3413
- addSubtitlesToTimeline(response.subtitles || []);
3440
+ addCaptionsToTimeline(response.captions || []);
3414
3441
  setTimeout(() => {
3415
3442
  setPollingStatus("idle");
3416
3443
  }, 3e3);
@@ -3419,32 +3446,32 @@ function GenerateSubtitlesPanel({
3419
3446
  stopPolling();
3420
3447
  setPollingStatus("error");
3421
3448
  setIsGenerating(false);
3422
- setErrorMessage(response.error || "Failed to generate subtitles");
3423
- console.error("Error generating subtitles:", response.error);
3449
+ setErrorMessage(response.error || "Failed to generate captions");
3450
+ console.error("Error generating captions:", response.error);
3424
3451
  }
3425
3452
  } catch (error) {
3426
3453
  stopPolling();
3427
3454
  setPollingStatus("error");
3428
3455
  setIsGenerating(false);
3429
- setErrorMessage(error instanceof Error ? error.message : "Failed to get subtitle status");
3430
- console.error("Error polling for subtitles:", error);
3456
+ setErrorMessage(error instanceof Error ? error.message : "Failed to get caption status");
3457
+ console.error("Error polling for captions:", error);
3431
3458
  }
3432
3459
  };
3433
3460
  await poll();
3434
3461
  pollingIntervalRef.current = setInterval(poll, 2e3);
3435
3462
  };
3436
- const handleGenerateSubtitles = async () => {
3463
+ const handleGenerateCaptions = async () => {
3437
3464
  if (!(selectedElement instanceof VideoElement)) {
3438
3465
  return;
3439
3466
  }
3440
3467
  const videoElement = selectedElement;
3441
3468
  try {
3442
- const reqId = await onGenerateSubtitles(videoElement);
3469
+ const reqId = await onGenerateCaptions(videoElement);
3443
3470
  if (!reqId) {
3444
3471
  setPollingStatus("error");
3445
3472
  setIsGenerating(false);
3446
- setErrorMessage("Failed to start subtitle generation");
3447
- console.error("Error generating subtitles: Failed to start subtitle generation");
3473
+ setErrorMessage("Failed to start caption generation");
3474
+ console.error("Error generating captions: Failed to start caption generation");
3448
3475
  return;
3449
3476
  }
3450
3477
  currentReqIdRef.current = reqId;
@@ -3452,8 +3479,8 @@ function GenerateSubtitlesPanel({
3452
3479
  } catch (error) {
3453
3480
  setPollingStatus("error");
3454
3481
  setIsGenerating(false);
3455
- setErrorMessage(error instanceof Error ? error.message : "Failed to start subtitle generation");
3456
- console.error("Error generating subtitles:", error);
3482
+ setErrorMessage(error instanceof Error ? error.message : "Failed to start caption generation");
3483
+ console.error("Error generating captions:", error);
3457
3484
  }
3458
3485
  };
3459
3486
  const checkAudio = async () => {
@@ -3485,7 +3512,7 @@ function GenerateSubtitlesPanel({
3485
3512
  setErrorMessage(null);
3486
3513
  }, [selectedElement]);
3487
3514
  return /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
3488
- /* @__PURE__ */ jsx("div", { className: "panel-title", children: "Generate Subtitles Panel" }),
3515
+ /* @__PURE__ */ jsx("div", { className: "panel-title", children: "Generate Captions Panel" }),
3489
3516
  isLoading && /* @__PURE__ */ jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxs("div", { className: "empty-state-content", children: [
3490
3517
  /* @__PURE__ */ jsx(LoaderCircle, { className: "empty-state-icon animate-spin" }),
3491
3518
  /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: "Checking for audio..." })
@@ -3496,85 +3523,228 @@ function GenerateSubtitlesPanel({
3496
3523
  ] }) }) }),
3497
3524
  !isLoading && containsAudio === true && pollingStatus === "idle" && !isGenerating && /* @__PURE__ */ jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxs("div", { className: "empty-state-content", children: [
3498
3525
  /* @__PURE__ */ jsx(Volume2, { className: "empty-state-icon" }),
3499
- /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: "Audio detected! You can now generate subtitles" })
3526
+ /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: "Audio detected! You can now generate captions" })
3500
3527
  ] }) }) }),
3501
3528
  !isLoading && isGenerating && pollingStatus === "polling" && /* @__PURE__ */ jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxs("div", { className: "empty-state-content", children: [
3502
3529
  /* @__PURE__ */ jsx(LoaderCircle, { className: "empty-state-icon animate-spin" }),
3503
- /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: "Generating subtitles... Please wait" })
3530
+ /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: "Generating captions... Please wait" })
3504
3531
  ] }) }) }),
3505
3532
  !isLoading && pollingStatus === "success" && /* @__PURE__ */ jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxs("div", { className: "empty-state-content", children: [
3506
3533
  /* @__PURE__ */ jsx(CircleCheck, { className: "empty-state-icon", color: "var(--color-green-500)" }),
3507
- /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: "Subtitles generated successfully!" })
3534
+ /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: "Captions generated successfully!" })
3508
3535
  ] }) }) }),
3509
3536
  !isLoading && pollingStatus === "error" && /* @__PURE__ */ jsx("div", { className: "panel-section", children: /* @__PURE__ */ jsx("div", { className: "empty-state", children: /* @__PURE__ */ jsxs("div", { className: "empty-state-content", children: [
3510
3537
  /* @__PURE__ */ jsx(CircleX, { className: "empty-state-icon", color: "var(--color-red-500)" }),
3511
- /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: errorMessage || "Failed to generate subtitles" })
3538
+ /* @__PURE__ */ jsx("p", { className: "empty-state-text", children: errorMessage || "Failed to generate captions" })
3512
3539
  ] }) }) }),
3513
3540
  !isLoading && /* @__PURE__ */ jsx("div", { className: "flex panel-section", children: /* @__PURE__ */ jsx(
3514
3541
  "button",
3515
3542
  {
3516
- onClick: handleGenerateSubtitles,
3543
+ onClick: handleGenerateCaptions,
3517
3544
  disabled: !containsAudio || isGenerating,
3518
3545
  className: "btn-primary w-full",
3519
- children: isGenerating ? "Generating..." : "Generate Subtitles"
3546
+ children: isGenerating ? "Generating..." : "Generate Captions"
3520
3547
  }
3521
3548
  ) })
3522
3549
  ] });
3523
3550
  }
3524
- function PropertiesPanelContainer({
3525
- selectedProp,
3551
+ function TextPropsPanel({
3526
3552
  selectedElement,
3527
- updateElement,
3528
- addSubtitlesToTimeline,
3529
- onGenerateSubtitles,
3530
- getSubtitleStatus
3553
+ updateElement
3531
3554
  }) {
3532
- if (!selectedElement) {
3533
- return /* @__PURE__ */ jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsx("div", { className: "properties-header", children: /* @__PURE__ */ jsx("h3", { className: "properties-title", children: "Select Element to see properties" }) }) });
3534
- }
3535
- if (selectedElement.getType() === "caption") {
3536
- return /* @__PURE__ */ jsx("div", { className: "panel-container", children: /* @__PURE__ */ jsx("div", { className: "properties-header", children: /* @__PURE__ */ jsx("h3", { className: "properties-title", children: "Not available for sub-title" }) }) });
3537
- }
3538
- return /* @__PURE__ */ jsxs(Fragment, { children: [
3539
- selectedProp === "element-props" && /* @__PURE__ */ jsx(
3540
- ElementProps,
3541
- {
3542
- selectedElement,
3543
- updateElement
3544
- }
3545
- ),
3546
- selectedProp === "playback-props" && /* @__PURE__ */ jsx(
3547
- PlaybackPropsPanel,
3548
- {
3549
- selectedElement,
3550
- updateElement
3551
- }
3552
- ),
3553
- selectedProp === "text-effects" && /* @__PURE__ */ jsx(
3554
- TextEffects,
3555
- {
3556
- selectedElement,
3557
- updateElement
3558
- }
3559
- ),
3560
- selectedProp === "animations" && /* @__PURE__ */ jsx(
3561
- Animation,
3562
- {
3563
- selectedElement,
3564
- updateElement
3565
- }
3566
- ),
3567
- selectedProp === "generate-subtitles" && /* @__PURE__ */ jsx(
3568
- GenerateSubtitlesPanel,
3555
+ if (!(selectedElement instanceof TextElement)) return null;
3556
+ const textProps = selectedElement.getProps() || {};
3557
+ const [isTypographyOpen, setIsTypographyOpen] = useState(false);
3558
+ const currentAlign = textProps.textAlign ?? "center";
3559
+ const currentWeight = textProps.fontWeight ?? 400;
3560
+ const isBold = currentWeight >= 600;
3561
+ const isItalic = textProps.fontStyle === "italic";
3562
+ const handleUpdate = (patch) => {
3563
+ if (!selectedElement) return;
3564
+ const next = { ...textProps, ...patch };
3565
+ selectedElement.setProps(next);
3566
+ updateElement == null ? void 0 : updateElement(selectedElement);
3567
+ };
3568
+ const toggleBold = () => {
3569
+ handleUpdate({ fontWeight: isBold ? 400 : 700 });
3570
+ };
3571
+ const toggleItalic = () => {
3572
+ handleUpdate({ fontStyle: isItalic ? "normal" : "italic" });
3573
+ };
3574
+ const setAlign = (align) => {
3575
+ handleUpdate({ textAlign: align });
3576
+ };
3577
+ return /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
3578
+ /* @__PURE__ */ jsx("div", { className: "panel-title", children: "Typography" }),
3579
+ /* @__PURE__ */ jsx(
3580
+ AccordionItem,
3569
3581
  {
3570
- selectedElement,
3571
- addSubtitlesToTimeline,
3572
- onGenerateSubtitles,
3573
- getSubtitleStatus
3582
+ title: "Typography",
3583
+ icon: /* @__PURE__ */ jsx(Type, { className: "icon-sm" }),
3584
+ isOpen: isTypographyOpen,
3585
+ onToggle: () => setIsTypographyOpen((open) => !open),
3586
+ children: /* @__PURE__ */ jsxs("div", { className: "properties-group", children: [
3587
+ /* @__PURE__ */ jsx("div", { className: "property-section", children: /* @__PURE__ */ jsx(
3588
+ PropertyRow,
3589
+ {
3590
+ label: "Font size",
3591
+ secondary: /* @__PURE__ */ jsxs("span", { children: [
3592
+ textProps.fontSize ?? 48,
3593
+ "px"
3594
+ ] }),
3595
+ children: /* @__PURE__ */ jsx(
3596
+ "input",
3597
+ {
3598
+ type: "range",
3599
+ min: 8,
3600
+ max: 160,
3601
+ value: textProps.fontSize ?? 48,
3602
+ onChange: (e) => handleUpdate({ fontSize: Number(e.target.value) }),
3603
+ className: "slider-purple"
3604
+ }
3605
+ )
3606
+ }
3607
+ ) }),
3608
+ /* @__PURE__ */ jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxs(PropertyRow, { label: "Style", children: [
3609
+ /* @__PURE__ */ jsx(
3610
+ "button",
3611
+ {
3612
+ type: "button",
3613
+ className: `form-btn ${isBold ? "active" : ""}`,
3614
+ onClick: toggleBold,
3615
+ title: "Bold",
3616
+ children: /* @__PURE__ */ jsx(Bold, { className: "icon-sm" })
3617
+ }
3618
+ ),
3619
+ /* @__PURE__ */ jsx(
3620
+ "button",
3621
+ {
3622
+ type: "button",
3623
+ className: `form-btn ${isItalic ? "active" : ""}`,
3624
+ onClick: toggleItalic,
3625
+ title: "Italic",
3626
+ children: /* @__PURE__ */ jsx(Italic, { className: "icon-sm" })
3627
+ }
3628
+ )
3629
+ ] }) }),
3630
+ /* @__PURE__ */ jsx("div", { className: "property-section", children: /* @__PURE__ */ jsxs(PropertyRow, { label: "Align", children: [
3631
+ /* @__PURE__ */ jsx(
3632
+ "button",
3633
+ {
3634
+ type: "button",
3635
+ className: `form-btn ${currentAlign === "left" ? "active" : ""}`,
3636
+ onClick: () => setAlign("left"),
3637
+ title: "Align left",
3638
+ children: /* @__PURE__ */ jsx(AlignLeft, { className: "icon-sm" })
3639
+ }
3640
+ ),
3641
+ /* @__PURE__ */ jsx(
3642
+ "button",
3643
+ {
3644
+ type: "button",
3645
+ className: `form-btn ${currentAlign === "center" ? "active" : ""}`,
3646
+ onClick: () => setAlign("center"),
3647
+ title: "Align center",
3648
+ children: /* @__PURE__ */ jsx(AlignCenter, { className: "icon-sm" })
3649
+ }
3650
+ ),
3651
+ /* @__PURE__ */ jsx(
3652
+ "button",
3653
+ {
3654
+ type: "button",
3655
+ className: `form-btn ${currentAlign === "right" ? "active" : ""}`,
3656
+ onClick: () => setAlign("right"),
3657
+ title: "Align right",
3658
+ children: /* @__PURE__ */ jsx(AlignRight, { className: "icon-sm" })
3659
+ }
3660
+ )
3661
+ ] }) })
3662
+ ] })
3574
3663
  }
3575
3664
  )
3576
3665
  ] });
3577
3666
  }
3667
+ function PropertiesPanelContainer({
3668
+ selectedElement,
3669
+ updateElement,
3670
+ addCaptionsToTimeline,
3671
+ onGenerateCaptions,
3672
+ getCaptionstatus,
3673
+ videoResolution
3674
+ }) {
3675
+ const title = selectedElement instanceof TextElement ? selectedElement.getText() : (selectedElement == null ? void 0 : selectedElement.getName()) || (selectedElement == null ? void 0 : selectedElement.getType()) || "Element";
3676
+ return /* @__PURE__ */ jsxs("aside", { className: "properties-panel", "aria-label": "Element properties inspector", children: [
3677
+ /* @__PURE__ */ jsxs("div", { className: "properties-header", children: [
3678
+ !selectedElement && /* @__PURE__ */ jsx("h3", { className: "properties-title", children: "Composition" }),
3679
+ selectedElement && selectedElement.getType() === "caption" && /* @__PURE__ */ jsx("h3", { className: "properties-title", children: "Subtitles are edited from the captions panel" }),
3680
+ selectedElement && selectedElement.getType() !== "caption" && /* @__PURE__ */ jsx("h3", { className: "properties-title", children: title })
3681
+ ] }),
3682
+ /* @__PURE__ */ jsxs("div", { className: "prop-content", children: [
3683
+ !selectedElement && /* @__PURE__ */ jsxs("div", { className: "panel-container", children: [
3684
+ /* @__PURE__ */ jsx("div", { className: "panel-title", children: "Canvas & Render" }),
3685
+ /* @__PURE__ */ jsx("div", { className: "properties-group", children: /* @__PURE__ */ jsxs("div", { className: "property-section", children: [
3686
+ /* @__PURE__ */ jsx("span", { className: "property-label", children: "Size" }),
3687
+ /* @__PURE__ */ jsxs("span", { className: "properties-size-readonly", children: [
3688
+ videoResolution.width,
3689
+ " × ",
3690
+ videoResolution.height
3691
+ ] })
3692
+ ] }) })
3693
+ ] }),
3694
+ selectedElement && selectedElement.getType() === "caption" ? null : selectedElement && /* @__PURE__ */ jsx(Fragment, { children: (() => {
3695
+ const isText = selectedElement instanceof TextElement;
3696
+ const isVideo = selectedElement instanceof VideoElement;
3697
+ const isAudio = selectedElement instanceof AudioElement;
3698
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3699
+ isText && /* @__PURE__ */ jsx(
3700
+ TextPropsPanel,
3701
+ {
3702
+ selectedElement,
3703
+ updateElement
3704
+ }
3705
+ ),
3706
+ !isAudio && /* @__PURE__ */ jsx(
3707
+ ElementProps,
3708
+ {
3709
+ selectedElement,
3710
+ updateElement
3711
+ }
3712
+ ),
3713
+ (isVideo || isAudio) && /* @__PURE__ */ jsx(
3714
+ PlaybackPropsPanel,
3715
+ {
3716
+ selectedElement,
3717
+ updateElement
3718
+ }
3719
+ ),
3720
+ isText && /* @__PURE__ */ jsx(
3721
+ TextEffects,
3722
+ {
3723
+ selectedElement,
3724
+ updateElement
3725
+ }
3726
+ ),
3727
+ !isAudio && /* @__PURE__ */ jsx(
3728
+ Animation,
3729
+ {
3730
+ selectedElement,
3731
+ updateElement
3732
+ }
3733
+ ),
3734
+ isVideo && /* @__PURE__ */ jsx(
3735
+ GenerateCaptionsPanel,
3736
+ {
3737
+ selectedElement,
3738
+ addCaptionsToTimeline,
3739
+ onGenerateCaptions,
3740
+ getCaptionstatus
3741
+ }
3742
+ )
3743
+ ] });
3744
+ })() })
3745
+ ] })
3746
+ ] });
3747
+ }
3578
3748
  const useStudioOperation = (studioConfig) => {
3579
3749
  const { editor, present, videoResolution } = useTimelineContext();
3580
3750
  const { setSeekTime, setPlayerState } = useLivePlayerContext();
@@ -3637,30 +3807,30 @@ const useStudioOperation = (studioConfig) => {
3637
3807
  alert("Export video not supported in demo mode");
3638
3808
  }
3639
3809
  };
3640
- const onGenerateSubtitles = async (videoElement) => {
3641
- if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
3642
- const service = studioConfig.subtitleGenerationService;
3643
- const reqId = await service.generateSubtitles(videoElement, present);
3810
+ const onGenerateCaptions = async (videoElement) => {
3811
+ if (studioConfig == null ? void 0 : studioConfig.captionGenerationService) {
3812
+ const service = studioConfig.captionGenerationService;
3813
+ const reqId = await service.generateCaptions(videoElement, present);
3644
3814
  return reqId;
3645
3815
  }
3646
- alert("Generate subtitles not supported in demo mode");
3816
+ alert("Generate captions not supported in demo mode");
3647
3817
  return null;
3648
3818
  };
3649
- const addSubtitlesToTimeline = (subtitles) => {
3819
+ const addCaptionsToTimeline = (captions) => {
3650
3820
  var _a;
3651
- const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) == null ? void 0 : _a.updateProjectWithSubtitles(subtitles);
3821
+ const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.captionGenerationService) == null ? void 0 : _a.updateProjectWithCaptions(captions);
3652
3822
  if (updatedProjectJSON) {
3653
3823
  editor.loadProject(updatedProjectJSON);
3654
3824
  }
3655
3825
  };
3656
- const getSubtitleStatus = async (reqId) => {
3657
- if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
3658
- const service = studioConfig.subtitleGenerationService;
3826
+ const getCaptionstatus = async (reqId) => {
3827
+ if (studioConfig == null ? void 0 : studioConfig.captionGenerationService) {
3828
+ const service = studioConfig.captionGenerationService;
3659
3829
  return await service.getRequestStatus(reqId);
3660
3830
  }
3661
3831
  return {
3662
3832
  status: "failed",
3663
- error: "Subtitle generation service not found"
3833
+ error: "Caption generation service not found"
3664
3834
  };
3665
3835
  };
3666
3836
  return {
@@ -3668,48 +3838,9 @@ const useStudioOperation = (studioConfig) => {
3668
3838
  onSaveProject,
3669
3839
  onExportVideo,
3670
3840
  onNewProject,
3671
- onGenerateSubtitles,
3672
- addSubtitlesToTimeline,
3673
- getSubtitleStatus
3674
- };
3675
- };
3676
- const useGenerateSubtitles = (studioConfig) => {
3677
- const { editor, present } = useTimelineContext();
3678
- const onGenerateSubtitles = async (videoElement) => {
3679
- if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
3680
- const service = studioConfig.subtitleGenerationService;
3681
- const reqId = await service.generateSubtitles(
3682
- videoElement,
3683
- present
3684
- );
3685
- return reqId;
3686
- }
3687
- alert("Generate subtitles not supported in demo mode");
3688
- return null;
3689
- };
3690
- const addSubtitlesToTimeline = (subtitles) => {
3691
- var _a;
3692
- const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) == null ? void 0 : _a.updateProjectWithSubtitles(
3693
- subtitles
3694
- );
3695
- if (updatedProjectJSON) {
3696
- editor.loadProject(updatedProjectJSON);
3697
- }
3698
- };
3699
- const getSubtitleStatus = async (reqId) => {
3700
- if (studioConfig == null ? void 0 : studioConfig.subtitleGenerationService) {
3701
- const service = studioConfig.subtitleGenerationService;
3702
- return await service.getRequestStatus(reqId);
3703
- }
3704
- return {
3705
- status: "failed",
3706
- error: "Subtitle generation service not found"
3707
- };
3708
- };
3709
- return {
3710
- onGenerateSubtitles,
3711
- addSubtitlesToTimeline,
3712
- getSubtitleStatus
3841
+ onGenerateCaptions,
3842
+ addCaptionsToTimeline,
3843
+ getCaptionstatus
3713
3844
  };
3714
3845
  };
3715
3846
  function TwickStudio({ studioConfig }) {
@@ -3717,8 +3848,6 @@ function TwickStudio({ studioConfig }) {
3717
3848
  const {
3718
3849
  selectedTool,
3719
3850
  setSelectedTool,
3720
- selectedProp,
3721
- setSelectedProp,
3722
3851
  selectedElement,
3723
3852
  addElement,
3724
3853
  updateElement
@@ -3730,7 +3859,7 @@ function TwickStudio({ studioConfig }) {
3730
3859
  onSaveProject,
3731
3860
  onExportVideo
3732
3861
  } = useStudioOperation(studioConfig);
3733
- const { onGenerateSubtitles, addSubtitlesToTimeline, getSubtitleStatus } = useGenerateSubtitles(studioConfig);
3862
+ const { onGenerateCaptions, addCaptionsToTimeline, getCaptionstatus } = useGenerateCaptions(studioConfig);
3734
3863
  const twickStudiConfig = useMemo(
3735
3864
  () => ({
3736
3865
  canvasMode: true,
@@ -3762,7 +3891,7 @@ function TwickStudio({ studioConfig }) {
3762
3891
  setSelectedTool
3763
3892
  }
3764
3893
  ),
3765
- /* @__PURE__ */ jsx(
3894
+ /* @__PURE__ */ jsx("div", { className: "studio-left-panel", children: /* @__PURE__ */ jsx(
3766
3895
  ElementPanelContainer,
3767
3896
  {
3768
3897
  videoResolution,
@@ -3772,7 +3901,7 @@ function TwickStudio({ studioConfig }) {
3772
3901
  addElement,
3773
3902
  updateElement
3774
3903
  }
3775
- ),
3904
+ ) }),
3776
3905
  /* @__PURE__ */ jsx("main", { className: "main-container", children: /* @__PURE__ */ jsx("div", { className: "canvas-wrapper", children: /* @__PURE__ */ jsx(
3777
3906
  "div",
3778
3907
  {
@@ -3783,28 +3912,59 @@ function TwickStudio({ studioConfig }) {
3783
3912
  children: /* @__PURE__ */ jsx(VideoEditor, { editorConfig: twickStudiConfig })
3784
3913
  }
3785
3914
  ) }) }),
3786
- /* @__PURE__ */ jsx(
3915
+ /* @__PURE__ */ jsx("div", { className: "studio-right-panel", children: /* @__PURE__ */ jsx(
3787
3916
  PropertiesPanelContainer,
3788
3917
  {
3789
- selectedProp,
3790
3918
  selectedElement,
3791
3919
  updateElement,
3792
- addSubtitlesToTimeline,
3793
- onGenerateSubtitles,
3794
- getSubtitleStatus
3795
- }
3796
- ),
3797
- /* @__PURE__ */ jsx(
3798
- PropsToolbar,
3799
- {
3800
- selectedElement,
3801
- selectedProp,
3802
- setSelectedProp
3920
+ addCaptionsToTimeline,
3921
+ onGenerateCaptions,
3922
+ getCaptionstatus,
3923
+ videoResolution
3803
3924
  }
3804
- )
3925
+ ) })
3805
3926
  ] })
3806
3927
  ] }) });
3807
3928
  }
3929
+ const useGenerateCaptions = (studioConfig) => {
3930
+ const { editor, present } = useTimelineContext();
3931
+ const onGenerateCaptions = async (videoElement) => {
3932
+ if (studioConfig == null ? void 0 : studioConfig.captionGenerationService) {
3933
+ const service = studioConfig.captionGenerationService;
3934
+ const reqId = await service.generateCaptions(
3935
+ videoElement,
3936
+ present
3937
+ );
3938
+ return reqId;
3939
+ }
3940
+ alert("Generate captions not supported in demo mode");
3941
+ return null;
3942
+ };
3943
+ const addCaptionsToTimeline = (captions) => {
3944
+ var _a;
3945
+ const updatedProjectJSON = (_a = studioConfig == null ? void 0 : studioConfig.captionGenerationService) == null ? void 0 : _a.updateProjectWithCaptions(
3946
+ captions
3947
+ );
3948
+ if (updatedProjectJSON) {
3949
+ editor.loadProject(updatedProjectJSON);
3950
+ }
3951
+ };
3952
+ const getCaptionstatus = async (reqId) => {
3953
+ if (studioConfig == null ? void 0 : studioConfig.captionGenerationService) {
3954
+ const service = studioConfig.captionGenerationService;
3955
+ return await service.getRequestStatus(reqId);
3956
+ }
3957
+ return {
3958
+ status: "failed",
3959
+ error: "Caption generation service not found"
3960
+ };
3961
+ };
3962
+ return {
3963
+ onGenerateCaptions,
3964
+ addCaptionsToTimeline,
3965
+ getCaptionstatus
3966
+ };
3967
+ };
3808
3968
  export {
3809
3969
  ANIMATIONS2 as ANIMATIONS,
3810
3970
  AudioElement2 as AudioElement,
@@ -3817,6 +3977,7 @@ export {
3817
3977
  CAPTION_STYLE2 as CAPTION_STYLE,
3818
3978
  CAPTION_STYLE_OPTIONS,
3819
3979
  CaptionElement2 as CaptionElement,
3980
+ CaptionsPanel,
3820
3981
  CircleElement2 as CircleElement,
3821
3982
  CirclePanel,
3822
3983
  ElementAdder,
@@ -3831,8 +3992,7 @@ export {
3831
3992
  ElementUpdater,
3832
3993
  ElementValidator,
3833
3994
  INITIAL_TIMELINE_DATA,
3834
- IconElement2 as IconElement,
3835
- IconPanel,
3995
+ IconElement,
3836
3996
  ImageElement2 as ImageElement,
3837
3997
  ImagePanel,
3838
3998
  LivePlayer,
@@ -3843,7 +4003,6 @@ export {
3843
4003
  RectElement2 as RectElement,
3844
4004
  RectPanel,
3845
4005
  StudioHeader,
3846
- SubtitlesPanel,
3847
4006
  TEXT_EFFECTS2 as TEXT_EFFECTS,
3848
4007
  TIMELINE_ACTION,
3849
4008
  TIMELINE_ELEMENT_TYPE,
@@ -3872,10 +4031,11 @@ export {
3872
4031
  isTrackId,
3873
4032
  setElementColors,
3874
4033
  useEditorManager2 as useEditorManager,
3875
- useGenerateSubtitles,
4034
+ useGenerateCaptions,
3876
4035
  useLivePlayerContext2 as useLivePlayerContext,
3877
4036
  usePlayerControl,
3878
4037
  useStudioManager,
4038
+ useTimelineContext2 as useTimelineContext,
3879
4039
  useTimelineControl
3880
4040
  };
3881
4041
  //# sourceMappingURL=index.mjs.map