@hyperframes/studio 0.4.16 → 0.4.17

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.
@@ -10,10 +10,14 @@ import { formatTime } from "../lib/time";
10
10
  import { TimelineClip } from "./TimelineClip";
11
11
  import { EditPopover } from "./EditModal";
12
12
  import {
13
+ buildClipRangeSelection,
13
14
  getTimelineEditCapabilities,
15
+ resolveBlockedTimelineEditIntent,
14
16
  resolveTimelineAutoScroll,
15
17
  resolveTimelineMove,
16
18
  resolveTimelineResize,
19
+ type BlockedTimelineEditIntent,
20
+ type TimelineRangeSelection,
17
21
  } from "./timelineEditing";
18
22
  import {
19
23
  defaultTimelineTheme,
@@ -29,6 +33,8 @@ const GUTTER = 32;
29
33
  const TRACK_H = 72;
30
34
  const RULER_H = 24;
31
35
  const CLIP_Y = 3; // vertical inset inside track
36
+ const CLIP_HANDLE_W = 18;
37
+ const TIMELINE_SCROLL_BUFFER = 24;
32
38
 
33
39
  interface TrackVisualStyle extends TimelineTrackStyle {
34
40
  icon: ReactNode;
@@ -130,6 +136,10 @@ export function getTimelinePlayheadLeft(time: number, pixelsPerSecond: number):
130
136
  return GUTTER + Math.max(0, time) * Math.max(0, pixelsPerSecond);
131
137
  }
132
138
 
139
+ export function getTimelineCanvasHeight(trackCount: number): number {
140
+ return RULER_H + Math.max(0, trackCount) * TRACK_H + TIMELINE_SCROLL_BUFFER;
141
+ }
142
+
133
143
  /* ── Component ──────────────────────────────────────────────────── */
134
144
  interface TimelineProps {
135
145
  /** Called when user seeks via ruler/track click or playhead drag */
@@ -157,6 +167,10 @@ interface TimelineProps {
157
167
  "start" | "duration" | "playbackStart"
158
168
  >,
159
169
  ) => Promise<void> | void;
170
+ onBlockedEditAttempt?: (
171
+ element: import("../store/playerStore").TimelineElement,
172
+ intent: BlockedTimelineEditIntent,
173
+ ) => void;
160
174
  theme?: Partial<TimelineTheme>;
161
175
  }
162
176
 
@@ -185,6 +199,14 @@ interface ResizingClipState {
185
199
  started: boolean;
186
200
  }
187
201
 
202
+ interface BlockedClipState {
203
+ element: TimelineElement;
204
+ intent: BlockedTimelineEditIntent;
205
+ originClientX: number;
206
+ originClientY: number;
207
+ started: boolean;
208
+ }
209
+
188
210
  export const Timeline = memo(function Timeline({
189
211
  onSeek,
190
212
  onDrillDown,
@@ -193,6 +215,7 @@ export const Timeline = memo(function Timeline({
193
215
  onFileDrop,
194
216
  onMoveElement,
195
217
  onResizeElement,
218
+ onBlockedEditAttempt,
196
219
  theme: themeOverrides,
197
220
  }: TimelineProps = {}) {
198
221
  const theme = useMemo(() => ({ ...defaultTimelineTheme, ...themeOverrides }), [themeOverrides]);
@@ -210,6 +233,11 @@ export const Timeline = memo(function Timeline({
210
233
  const scrollRef = useRef<HTMLDivElement>(null);
211
234
  const [hoveredClip, setHoveredClip] = useState<string | null>(null);
212
235
  const isDragging = useRef(false);
236
+ const shiftClickClipRef = useRef<{
237
+ element: TimelineElement;
238
+ anchorX: number;
239
+ anchorY: number;
240
+ } | null>(null);
213
241
  // Range selection (Shift+drag)
214
242
  const [shiftHeld, setShiftHeld] = useState(false);
215
243
  useMountEffect(() => {
@@ -227,18 +255,14 @@ export const Timeline = memo(function Timeline({
227
255
  });
228
256
  const isRangeSelecting = useRef(false);
229
257
  const rangeAnchorTime = useRef(0);
230
- const [rangeSelection, setRangeSelection] = useState<{
231
- start: number;
232
- end: number;
233
- anchorX: number;
234
- anchorY: number;
235
- } | null>(null);
258
+ const [rangeSelection, setRangeSelection] = useState<TimelineRangeSelection | null>(null);
236
259
  const [draggedClip, setDraggedClip] = useState<DraggedClipState | null>(null);
237
260
  const draggedClipRef = useRef<DraggedClipState | null>(null);
238
261
  draggedClipRef.current = draggedClip;
239
262
  const [resizingClip, setResizingClip] = useState<ResizingClipState | null>(null);
240
263
  const resizingClipRef = useRef<ResizingClipState | null>(null);
241
264
  resizingClipRef.current = resizingClip;
265
+ const blockedClipRef = useRef<BlockedClipState | null>(null);
242
266
  const onMoveElementRef = useRef(onMoveElement);
243
267
  onMoveElementRef.current = onMoveElement;
244
268
  const onResizeElementRef = useRef(onResizeElement);
@@ -546,6 +570,7 @@ export const Timeline = memo(function Timeline({
546
570
  const handleWindowPointerMove = (e: PointerEvent) => {
547
571
  const drag = draggedClipRef.current;
548
572
  const resize = resizingClipRef.current;
573
+ const blocked = blockedClipRef.current;
549
574
  if (resize) {
550
575
  const distance = Math.abs(e.clientX - resize.originClientX);
551
576
  if (!resize.started && distance < 2) return;
@@ -561,6 +586,8 @@ export const Timeline = memo(function Timeline({
561
586
  Math.max(resize.element.playbackRate ?? 1, 0.1),
562
587
  )
563
588
  : Number.POSITIVE_INFINITY;
589
+ const normalizedTag = resize.element.tag.toLowerCase();
590
+ const canSeedPlaybackStart = normalizedTag === "audio" || normalizedTag === "video";
564
591
  const nextResize = resolveTimelineResize(
565
592
  {
566
593
  start: resize.element.start,
@@ -569,7 +596,10 @@ export const Timeline = memo(function Timeline({
569
596
  pixelsPerSecond: ppsRef.current,
570
597
  minStart: 0,
571
598
  maxEnd: Math.min(durationRef.current, resize.element.start + sourceRemaining),
572
- playbackStart: resize.element.playbackStart,
599
+ playbackStart:
600
+ resize.edge === "start" && canSeedPlaybackStart
601
+ ? (resize.element.playbackStart ?? 0)
602
+ : resize.element.playbackStart,
573
603
  playbackRate: resize.element.playbackRate,
574
604
  },
575
605
  resize.edge,
@@ -589,6 +619,23 @@ export const Timeline = memo(function Timeline({
589
619
  );
590
620
  return;
591
621
  }
622
+ if (blocked) {
623
+ const distance = Math.hypot(
624
+ e.clientX - blocked.originClientX,
625
+ e.clientY - blocked.originClientY,
626
+ );
627
+ const threshold = blocked.intent === "move" ? 4 : 2;
628
+ if (!blocked.started && distance < threshold) return;
629
+ if (!blocked.started) {
630
+ blocked.started = true;
631
+ blockedClipRef.current = blocked;
632
+ suppressClickRef.current = true;
633
+ setShowPopover(false);
634
+ setRangeSelection(null);
635
+ onBlockedEditAttempt?.(blocked.element, blocked.intent);
636
+ }
637
+ return;
638
+ }
592
639
  if (!drag) return;
593
640
 
594
641
  const distance = Math.hypot(e.clientX - drag.originClientX, e.clientY - drag.originClientY);
@@ -644,6 +691,14 @@ export const Timeline = memo(function Timeline({
644
691
  return;
645
692
  }
646
693
 
694
+ const blocked = blockedClipRef.current;
695
+ if (blocked) {
696
+ blockedClipRef.current = null;
697
+ if (!blocked.started) return;
698
+ clearSuppressedClick();
699
+ return;
700
+ }
701
+
647
702
  const drag = draggedClipRef.current;
648
703
  if (!drag) return;
649
704
  draggedClipRef.current = null;
@@ -707,6 +762,7 @@ export const Timeline = memo(function Timeline({
707
762
  return;
708
763
  }
709
764
 
765
+ shiftClickClipRef.current = null;
710
766
  // Normal click on a clip — let the clip handle it
711
767
  if ((e.target as HTMLElement).closest("[data-clip]")) return;
712
768
  (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
@@ -740,8 +796,14 @@ export const Timeline = memo(function Timeline({
740
796
  const handlePointerUp = useCallback(() => {
741
797
  if (isRangeSelecting.current) {
742
798
  isRangeSelecting.current = false;
743
- // Show popover if range is meaningful (> 0.2s)
799
+ const pendingShiftClick = shiftClickClipRef.current;
800
+ shiftClickClipRef.current = null;
744
801
  setRangeSelection((prev) => {
802
+ if (prev && pendingShiftClick && Math.abs(prev.end - prev.start) <= 0.2) {
803
+ setShowPopover(true);
804
+ return buildClipRangeSelection(pendingShiftClick.element, pendingShiftClick);
805
+ }
806
+ // Show popover if range is meaningful (> 0.2s)
745
807
  if (prev && Math.abs(prev.end - prev.start) > 0.2) {
746
808
  setShowPopover(true);
747
809
  return prev;
@@ -869,7 +931,7 @@ export const Timeline = memo(function Timeline({
869
931
  );
870
932
  }
871
933
 
872
- const totalH = RULER_H + displayTrackOrder.length * TRACK_H;
934
+ const totalH = getTimelineCanvasHeight(displayTrackOrder.length);
873
935
  const draggedElement = draggedClip?.element ?? null;
874
936
  const activeDraggedElement =
875
937
  draggedClip?.started === true && draggedElement
@@ -990,7 +1052,7 @@ export const Timeline = memo(function Timeline({
990
1052
  {shiftHeld && !rangeSelection && (
991
1053
  <div className="absolute inset-0 flex items-center justify-center pointer-events-none z-10">
992
1054
  <span className="text-[9px] font-medium" style={{ color: theme.textSecondary }}>
993
- Drag to select range
1055
+ Drag or click a clip to edit range
994
1056
  </span>
995
1057
  </div>
996
1058
  )}
@@ -1108,6 +1170,7 @@ export const Timeline = memo(function Timeline({
1108
1170
  if (edge === "start" && !capabilities.canTrimStart) return;
1109
1171
  if (edge === "end" && !capabilities.canTrimEnd) return;
1110
1172
  e.stopPropagation();
1173
+ blockedClipRef.current = null;
1111
1174
  setShowPopover(false);
1112
1175
  setRangeSelection(null);
1113
1176
  setResizingClip({
@@ -1121,16 +1184,41 @@ export const Timeline = memo(function Timeline({
1121
1184
  });
1122
1185
  }}
1123
1186
  onPointerDown={(e) => {
1187
+ if (e.button !== 0) return;
1188
+ if (e.shiftKey) {
1189
+ shiftClickClipRef.current = {
1190
+ element: el,
1191
+ anchorX: e.clientX,
1192
+ anchorY: e.clientY,
1193
+ };
1194
+ return;
1195
+ }
1196
+ const target = e.currentTarget as HTMLElement;
1197
+ const rect = target.getBoundingClientRect();
1198
+ const blockedIntent = resolveBlockedTimelineEditIntent({
1199
+ width: rect.width,
1200
+ offsetX: e.clientX - rect.left,
1201
+ handleWidth: CLIP_HANDLE_W,
1202
+ capabilities,
1203
+ });
1124
1204
  if (
1125
- e.button !== 0 ||
1126
- e.shiftKey ||
1127
- !onMoveElement ||
1128
- !capabilities.canMove
1129
- )
1205
+ blockedIntent &&
1206
+ ((blockedIntent === "move" && onMoveElement) ||
1207
+ (blockedIntent !== "move" && onResizeElement))
1208
+ ) {
1209
+ blockedClipRef.current = {
1210
+ element: el,
1211
+ intent: blockedIntent,
1212
+ originClientX: e.clientX,
1213
+ originClientY: e.clientY,
1214
+ started: false,
1215
+ };
1130
1216
  return;
1217
+ }
1218
+ if (!onMoveElement || !capabilities.canMove) return;
1219
+ blockedClipRef.current = null;
1131
1220
  setShowPopover(false);
1132
1221
  setRangeSelection(null);
1133
- const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
1134
1222
  setDraggedClip({
1135
1223
  element: el,
1136
1224
  originClientX: e.clientX,
@@ -1270,7 +1358,7 @@ export const Timeline = memo(function Timeline({
1270
1358
  Shift
1271
1359
  </kbd>
1272
1360
  <span className="text-[9px]" style={{ color: theme.textSecondary }}>
1273
- + drag to edit range
1361
+ + drag/click to edit range
1274
1362
  </span>
1275
1363
  </div>
1276
1364
  </div>
@@ -147,7 +147,7 @@ export const TimelineClip = memo(function TimelineClip({
147
147
  top: 0,
148
148
  bottom: 0,
149
149
  width: 18,
150
- opacity: showHandles ? 1 : 0,
150
+ opacity: showHandles && capabilities.canTrimEnd ? 1 : 0,
151
151
  pointerEvents: onResizeStart && capabilities.canTrimEnd ? "auto" : "none",
152
152
  zIndex: 4,
153
153
  transition: "opacity 120ms ease-out",
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import {
3
+ buildClipRangeSelection,
3
4
  buildPromptCopyText,
4
5
  buildTimelineElementAgentPrompt,
5
6
  buildTimelineAgentPrompt,
@@ -7,6 +8,7 @@ import {
7
8
  canOffsetTrimClipStart,
8
9
  getTimelineEditCapabilities,
9
10
  hasPatchableTimelineTarget,
11
+ resolveBlockedTimelineEditIntent,
10
12
  resolveTimelineAutoScroll,
11
13
  resolveTimelineMove,
12
14
  resolveTimelineResize,
@@ -199,6 +201,14 @@ describe("canOffsetTrimClipStart", () => {
199
201
  ).toBe(true);
200
202
  });
201
203
 
204
+ it("allows front trim for plain audio clips even before media-start exists", () => {
205
+ expect(
206
+ canOffsetTrimClipStart({
207
+ tag: "audio",
208
+ }),
209
+ ).toBe(true);
210
+ });
211
+
202
212
  it("blocks front trim for generic motion clips", () => {
203
213
  expect(
204
214
  canOffsetTrimClipStart({
@@ -223,6 +233,21 @@ describe("hasPatchableTimelineTarget", () => {
223
233
  });
224
234
 
225
235
  describe("getTimelineEditCapabilities", () => {
236
+ it("does not disable editable audio just because it spans multiple scenes", () => {
237
+ expect(
238
+ getTimelineEditCapabilities({
239
+ tag: "audio",
240
+ duration: 8,
241
+ selector: "#voiceover",
242
+ sourceDuration: 8,
243
+ }),
244
+ ).toEqual({
245
+ canMove: true,
246
+ canTrimStart: true,
247
+ canTrimEnd: true,
248
+ });
249
+ });
250
+
226
251
  it("disables move and trims for generic motion clips even when patchable", () => {
227
252
  expect(
228
253
  getTimelineEditCapabilities({
@@ -299,6 +324,111 @@ describe("getTimelineEditCapabilities", () => {
299
324
  });
300
325
  });
301
326
 
327
+ describe("resolveBlockedTimelineEditIntent", () => {
328
+ it("returns move when the clip body is blocked", () => {
329
+ expect(
330
+ resolveBlockedTimelineEditIntent({
331
+ width: 160,
332
+ offsetX: 80,
333
+ handleWidth: 18,
334
+ capabilities: {
335
+ canMove: false,
336
+ canTrimStart: false,
337
+ canTrimEnd: false,
338
+ },
339
+ }),
340
+ ).toBe("move");
341
+ });
342
+
343
+ it("returns resize-start when the left edge is blocked", () => {
344
+ expect(
345
+ resolveBlockedTimelineEditIntent({
346
+ width: 160,
347
+ offsetX: 8,
348
+ handleWidth: 18,
349
+ capabilities: {
350
+ canMove: false,
351
+ canTrimStart: false,
352
+ canTrimEnd: true,
353
+ },
354
+ }),
355
+ ).toBe("resize-start");
356
+ });
357
+
358
+ it("returns resize-end when the right edge is blocked", () => {
359
+ expect(
360
+ resolveBlockedTimelineEditIntent({
361
+ width: 160,
362
+ offsetX: 154,
363
+ handleWidth: 18,
364
+ capabilities: {
365
+ canMove: false,
366
+ canTrimStart: true,
367
+ canTrimEnd: false,
368
+ },
369
+ }),
370
+ ).toBe("resize-end");
371
+ });
372
+
373
+ it("does not block the left edge when the clip can still be moved", () => {
374
+ expect(
375
+ resolveBlockedTimelineEditIntent({
376
+ width: 160,
377
+ offsetX: 8,
378
+ handleWidth: 18,
379
+ capabilities: {
380
+ canMove: true,
381
+ canTrimStart: false,
382
+ canTrimEnd: true,
383
+ },
384
+ }),
385
+ ).toBe(null);
386
+ });
387
+
388
+ it("does not swallow the full surface of a narrow movable clip", () => {
389
+ expect(
390
+ resolveBlockedTimelineEditIntent({
391
+ width: 12,
392
+ offsetX: 6,
393
+ handleWidth: 18,
394
+ capabilities: {
395
+ canMove: true,
396
+ canTrimStart: false,
397
+ canTrimEnd: false,
398
+ },
399
+ }),
400
+ ).toBe(null);
401
+ });
402
+
403
+ it("returns null when the relevant edit is supported", () => {
404
+ expect(
405
+ resolveBlockedTimelineEditIntent({
406
+ width: 160,
407
+ offsetX: 8,
408
+ handleWidth: 18,
409
+ capabilities: {
410
+ canMove: true,
411
+ canTrimStart: true,
412
+ canTrimEnd: true,
413
+ },
414
+ }),
415
+ ).toBe(null);
416
+ });
417
+ });
418
+
419
+ describe("buildClipRangeSelection", () => {
420
+ it("anchors the full clip range at the click position", () => {
421
+ expect(
422
+ buildClipRangeSelection({ start: 1.25, duration: 3.5 }, { anchorX: 320, anchorY: 180 }),
423
+ ).toEqual({
424
+ start: 1.25,
425
+ end: 4.75,
426
+ anchorX: 320,
427
+ anchorY: 180,
428
+ });
429
+ });
430
+ });
431
+
302
432
  describe("resolveTimelineAutoScroll", () => {
303
433
  it("does not scroll when the pointer stays away from the edges", () => {
304
434
  expect(
@@ -420,6 +550,25 @@ describe("resolveTimelineResize", () => {
420
550
  ).toEqual({ start: 1.5, duration: 2.5, playbackStart: 1 });
421
551
  });
422
552
 
553
+ it("can seed front trim from an implicit zero playback start", () => {
554
+ expect(
555
+ resolveTimelineResize(
556
+ {
557
+ start: 0,
558
+ duration: 8,
559
+ originClientX: 100,
560
+ pixelsPerSecond: 100,
561
+ minStart: 0,
562
+ maxEnd: 8,
563
+ playbackStart: 0,
564
+ playbackRate: 1,
565
+ },
566
+ "start",
567
+ 200,
568
+ ),
569
+ ).toEqual({ start: 1, duration: 7, playbackStart: 1 });
570
+ });
571
+
423
572
  it("prevents extending media left past available source before media-start", () => {
424
573
  expect(
425
574
  resolveTimelineResize(
@@ -175,6 +175,15 @@ export interface TimelineEditCapabilities {
175
175
  canTrimEnd: boolean;
176
176
  }
177
177
 
178
+ export type BlockedTimelineEditIntent = "move" | "resize-start" | "resize-end";
179
+
180
+ export interface TimelineRangeSelection {
181
+ start: number;
182
+ end: number;
183
+ anchorX: number;
184
+ anchorY: number;
185
+ }
186
+
178
187
  function isDeterministicTimelineWindow(input: {
179
188
  tag: string;
180
189
  compositionSrc?: string;
@@ -207,12 +216,7 @@ export function canOffsetTrimClipStart(input: {
207
216
  if (input.playbackStartAttr != null) return true;
208
217
  if (input.playbackStart != null) return true;
209
218
  const normalizedTag = input.tag.toLowerCase();
210
- if (!["video", "audio"].includes(normalizedTag)) return false;
211
- return (
212
- input.sourceDuration != null &&
213
- Number.isFinite(input.sourceDuration) &&
214
- input.sourceDuration > 0
215
- );
219
+ return ["video", "audio"].includes(normalizedTag);
216
220
  }
217
221
 
218
222
  export function getTimelineEditCapabilities(input: {
@@ -235,6 +239,41 @@ export function getTimelineEditCapabilities(input: {
235
239
  };
236
240
  }
237
241
 
242
+ export function resolveBlockedTimelineEditIntent(input: {
243
+ width: number;
244
+ offsetX: number;
245
+ handleWidth: number;
246
+ capabilities: TimelineEditCapabilities;
247
+ }): BlockedTimelineEditIntent | null {
248
+ if (input.capabilities.canMove) {
249
+ return null;
250
+ }
251
+
252
+ const safeWidth = Math.max(0, input.width);
253
+ const safeOffsetX = clamp(input.offsetX, 0, safeWidth);
254
+ const safeHandleWidth = Math.max(0, input.handleWidth);
255
+
256
+ if (safeOffsetX <= safeHandleWidth && !input.capabilities.canTrimStart) {
257
+ return "resize-start";
258
+ }
259
+ if (safeOffsetX >= Math.max(0, safeWidth - safeHandleWidth) && !input.capabilities.canTrimEnd) {
260
+ return "resize-end";
261
+ }
262
+ return "move";
263
+ }
264
+
265
+ export function buildClipRangeSelection(
266
+ clip: { start: number; duration: number },
267
+ anchor: { anchorX: number; anchorY: number },
268
+ ): TimelineRangeSelection {
269
+ return {
270
+ start: clip.start,
271
+ end: clip.start + clip.duration,
272
+ anchorX: anchor.anchorX,
273
+ anchorY: anchor.anchorY,
274
+ };
275
+ }
276
+
238
277
  export function buildTimelineAgentPrompt({
239
278
  rangeStart,
240
279
  rangeEnd,
@@ -559,7 +559,11 @@ export function useTimelinePlayer() {
559
559
 
560
560
  // Convert a runtime timeline message (from iframe postMessage) into TimelineElements
561
561
  const processTimelineMessage = useCallback(
562
- (data: { clips: ClipManifestClip[]; durationInFrames: number }) => {
562
+ (data: {
563
+ clips: ClipManifestClip[];
564
+ durationInFrames: number;
565
+ scenes?: Array<{ id: string; label: string; start: number; duration: number }>;
566
+ }) => {
563
567
  if (!data.clips || data.clips.length === 0) {
564
568
  return;
565
569
  }
@@ -1 +0,0 @@
1
- *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.\!visible{visibility:visible!important}.visible{visibility:visible}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.bottom-1{bottom:.25rem}.bottom-2{bottom:.5rem}.bottom-6{bottom:1.5rem}.bottom-full{bottom:100%}.left-0{left:0}.left-1\/2{left:50%}.left-2{left:.5rem}.right-0{right:0}.right-3{right:.75rem}.top-0{top:0}.top-1{top:.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.top-full{top:100%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[1\]{z-index:1}.z-\[200\]{z-index:200}.z-\[2\]{z-index:2}.z-\[90\]{z-index:90}.z-\[91\]{z-index:91}.mx-1{margin-left:.25rem;margin-right:.25rem}.my-0\.5{margin-top:.125rem;margin-bottom:.125rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.mb-0\.5{margin-bottom:.125rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.ml-1\.5{margin-left:.375rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[1080px\]{height:1080px}.h-\[3px\]{height:3px}.h-\[45px\]{height:45px}.h-\[52px\]{height:52px}.h-\[5px\]{height:5px}.h-full{height:100%}.h-px{height:1px}.max-h-24{max-height:6rem}.max-h-\[70\%\]{max-height:70%}.max-h-\[80vh\]{max-height:80vh}.max-h-full{max-height:100%}.min-h-0{min-height:0px}.min-h-7{min-height:1.75rem}.min-h-8{min-height:2rem}.min-h-9{min-height:2.25rem}.w-1\.5{width:.375rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-52{width:13rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-80{width:20rem}.w-\[160px\]{width:160px}.w-\[1920px\]{width:1920px}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-7{min-width:1.75rem}.min-w-8{min-width:2rem}.min-w-9{min-width:2.25rem}.min-w-\[140px\]{min-width:140px}.min-w-\[160px\]{min-width:160px}.min-w-\[56px\]{min-width:56px}.min-w-\[58px\]{min-width:58px}.min-w-\[72px\]{min-width:72px}.max-w-\[280px\]{max-width:280px}.max-w-full{max-width:100%}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.grow{flex-grow:1}.-translate-x-1\/2{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-col-resize{cursor:col-resize}.cursor-crosshair{cursor:crosshair}.cursor-default{cursor:default}.cursor-help{cursor:help}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.resize-y{resize:vertical}.\!resize{resize:both!important}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-none{border-style:none}.border-green-500\/30{border-color:#22c55e4d}.border-neutral-600{--tw-border-opacity: 1;border-color:rgb(82 82 82 / var(--tw-border-opacity, 1))}.border-neutral-700{--tw-border-opacity: 1;border-color:rgb(64 64 64 / var(--tw-border-opacity, 1))}.border-neutral-700\/40{border-color:#40404066}.border-neutral-700\/50{border-color:#40404080}.border-neutral-700\/60{border-color:#40404099}.border-neutral-800{--tw-border-opacity: 1;border-color:rgb(38 38 38 / var(--tw-border-opacity, 1))}.border-neutral-800\/30{border-color:#2626264d}.border-neutral-800\/40{border-color:#26262666}.border-neutral-800\/50{border-color:#26262680}.border-neutral-800\/60{border-color:#26262699}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-red-700\/50{border-color:#b91c1c80}.border-studio-accent{--tw-border-opacity: 1;border-color:rgb(60 230 172 / var(--tw-border-opacity, 1))}.border-studio-accent\/20{border-color:#3ce6ac33}.border-studio-accent\/25{border-color:#3ce6ac40}.border-studio-accent\/30{border-color:#3ce6ac4d}.border-studio-accent\/50{border-color:#3ce6ac80}.border-studio-accent\/60{border-color:#3ce6ac99}.border-transparent{border-color:transparent}.border-white\/20{border-color:#fff3}.border-t-white{--tw-border-opacity: 1;border-top-color:rgb(255 255 255 / var(--tw-border-opacity, 1))}.bg-\[\#0a0a0b\]{--tw-bg-opacity: 1;background-color:rgb(10 10 11 / var(--tw-bg-opacity, 1))}.bg-\[\#3CE6AC\]\/10{background-color:#3ce6ac1a}.bg-\[\#3CE6AC\]\/5{background-color:#3ce6ac0d}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/20{background-color:#0003}.bg-black\/50{background-color:#00000080}.bg-black\/60{background-color:#0009}.bg-black\/80{background-color:#000c}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-neutral-600{--tw-bg-opacity: 1;background-color:rgb(82 82 82 / var(--tw-bg-opacity, 1))}.bg-neutral-700{--tw-bg-opacity: 1;background-color:rgb(64 64 64 / var(--tw-bg-opacity, 1))}.bg-neutral-700\/40{background-color:#40404066}.bg-neutral-800{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.bg-neutral-800\/60{background-color:#26262699}.bg-neutral-800\/70{background-color:#262626b3}.bg-neutral-900{--tw-bg-opacity: 1;background-color:rgb(23 23 23 / var(--tw-bg-opacity, 1))}.bg-neutral-900\/50{background-color:#17171780}.bg-neutral-950{--tw-bg-opacity: 1;background-color:rgb(10 10 10 / var(--tw-bg-opacity, 1))}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-red-900\/60{background-color:#7f1d1d99}.bg-red-900\/90{background-color:#7f1d1de6}.bg-red-950\/30{background-color:#450a0a4d}.bg-studio-accent{--tw-bg-opacity: 1;background-color:rgb(60 230 172 / var(--tw-bg-opacity, 1))}.bg-studio-accent\/10{background-color:#3ce6ac1a}.bg-studio-accent\/15{background-color:#3ce6ac26}.bg-studio-accent\/20{background-color:#3ce6ac33}.bg-studio-accent\/\[0\.03\]{background-color:#3ce6ac08}.bg-studio-accent\/\[0\.05\]{background-color:#3ce6ac0d}.bg-studio-accent\/\[0\.06\]{background-color:#3ce6ac0f}.bg-studio-accent\/\[0\.07\]{background-color:#3ce6ac12}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-white\/10{background-color:#ffffff1a}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-0\.5{padding-bottom:.125rem}.pb-3{padding-bottom:.75rem}.pt-1\.5{padding-top:.375rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-5{line-height:1.25rem}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-tight{line-height:1.25}.tracking-\[0\.08em\]{letter-spacing:.08em}.tracking-\[0\.16em\]{letter-spacing:.16em}.tracking-wider{letter-spacing:.05em}.text-\[\#09090B\]{--tw-text-opacity: 1;color:rgb(9 9 11 / var(--tw-text-opacity, 1))}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-neutral-100{--tw-text-opacity: 1;color:rgb(245 245 245 / var(--tw-text-opacity, 1))}.text-neutral-200{--tw-text-opacity: 1;color:rgb(229 229 229 / var(--tw-text-opacity, 1))}.text-neutral-300{--tw-text-opacity: 1;color:rgb(212 212 212 / var(--tw-text-opacity, 1))}.text-neutral-400{--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity, 1))}.text-neutral-500{--tw-text-opacity: 1;color:rgb(115 115 115 / var(--tw-text-opacity, 1))}.text-neutral-600{--tw-text-opacity: 1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.text-neutral-700{--tw-text-opacity: 1;color:rgb(64 64 64 / var(--tw-text-opacity, 1))}.text-neutral-950{--tw-text-opacity: 1;color:rgb(10 10 10 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-studio-accent{--tw-text-opacity: 1;color:rgb(60 230 172 / var(--tw-text-opacity, 1))}.text-studio-accent\/50{color:#3ce6ac80}.text-studio-accent\/80{color:#3ce6accc}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-white\/60{color:#fff9}.underline{text-decoration-line:underline}.line-through{text-decoration-line:line-through}.accent-studio-accent{accent-color:#3CE6AC}.opacity-25{opacity:.25}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-black\/40{--tw-shadow-color: rgb(0 0 0 / .4);--tw-shadow: var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.outline-1{outline-width:1px}.-outline-offset-1{outline-offset:-1px}.outline-\[\#3CE6AC\]\/30{outline-color:#3ce6ac4d}.outline-\[\#3CE6AC\]\/40{outline-color:#3ce6ac66}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-studio-accent{--tw-ring-opacity: 1;--tw-ring-color: rgb(60 230 172 / var(--tw-ring-opacity, 1))}.ring-white\/50{--tw-ring-color: rgb(255 255 255 / .5)}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow{--tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / .1)) drop-shadow(0 1px 1px rgb(0 0 0 / .06));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert: invert(100%);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-150{transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}body{margin:0;padding:0;background:#0a0a0a;color:#e5e5e5;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;overflow:hidden}#root{width:100vw;height:100vh;height:100dvh}.cm-editor{height:100%;font-size:13px}.cm-editor .cm-scroller{font-family:JetBrains Mono,Fira Code,SF Mono,monospace}.cm-editor.cm-focused{outline:none}.placeholder\:text-neutral-600::-moz-placeholder{--tw-text-opacity: 1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.placeholder\:text-neutral-600::placeholder{--tw-text-opacity: 1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.last\:border-0:last-child{border-width:0px}.hover\:border-neutral-500:hover{--tw-border-opacity: 1;border-color:rgb(115 115 115 / var(--tw-border-opacity, 1))}.hover\:border-neutral-600:hover{--tw-border-opacity: 1;border-color:rgb(82 82 82 / var(--tw-border-opacity, 1))}.hover\:border-neutral-700:hover{--tw-border-opacity: 1;border-color:rgb(64 64 64 / var(--tw-border-opacity, 1))}.hover\:border-studio-accent\/50:hover{border-color:#3ce6ac80}.hover\:bg-neutral-200:hover{--tw-bg-opacity: 1;background-color:rgb(229 229 229 / var(--tw-bg-opacity, 1))}.hover\:bg-neutral-600:hover{--tw-bg-opacity: 1;background-color:rgb(82 82 82 / var(--tw-bg-opacity, 1))}.hover\:bg-neutral-800:hover{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-neutral-800\/30:hover{background-color:#2626264d}.hover\:bg-neutral-800\/50:hover{background-color:#26262680}.hover\:bg-red-500:hover{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-red-800\/60:hover{background-color:#991b1b99}.hover\:bg-red-900\/30:hover{background-color:#7f1d1d4d}.hover\:bg-studio-accent\/25:hover{background-color:#3ce6ac40}.hover\:bg-studio-accent\/80:hover{background-color:#3ce6accc}.hover\:text-amber-300:hover{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.hover\:text-green-400:hover{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.hover\:text-neutral-100:hover{--tw-text-opacity: 1;color:rgb(245 245 245 / var(--tw-text-opacity, 1))}.hover\:text-neutral-200:hover{--tw-text-opacity: 1;color:rgb(229 229 229 / var(--tw-text-opacity, 1))}.hover\:text-neutral-300:hover{--tw-text-opacity: 1;color:rgb(212 212 212 / var(--tw-text-opacity, 1))}.hover\:text-neutral-400:hover{--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity, 1))}.hover\:text-red-400:hover{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.hover\:text-studio-accent:hover{--tw-text-opacity: 1;color:rgb(60 230 172 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:ring-1:hover{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.hover\:ring-white\/30:hover{--tw-ring-color: rgb(255 255 255 / .3)}.hover\:brightness-110:hover{--tw-brightness: brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.focus\:border-\[\#3CE6AC\]:focus{--tw-border-opacity: 1;border-color:rgb(60 230 172 / var(--tw-border-opacity, 1))}.focus\:border-neutral-600:focus{--tw-border-opacity: 1;border-color:rgb(82 82 82 / var(--tw-border-opacity, 1))}.focus\:border-studio-accent:focus{--tw-border-opacity: 1;border-color:rgb(60 230 172 / var(--tw-border-opacity, 1))}.focus\:border-studio-accent\/40:focus{border-color:#3ce6ac66}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.active\:scale-\[0\.97\]:active{--tw-scale-x: .97;--tw-scale-y: .97;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:scale-\[0\.98\]:active{--tw-scale-x: .98;--tw-scale-y: .98;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:scale-125{--tw-scale-x: 1.25;--tw-scale-y: 1.25;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@media(min-width:768px){.md\:inline{display:inline}}