@hyperframes/studio 0.6.85 → 0.6.87

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 (88) hide show
  1. package/dist/assets/{hyperframes-player-DRpY3xHh.js → hyperframes-player-0esDKGRk.js} +1 -1
  2. package/dist/assets/index-BA19FAPN.js +143 -0
  3. package/dist/assets/index-CGlIm_-E.css +1 -0
  4. package/dist/index.html +2 -2
  5. package/package.json +4 -4
  6. package/src/App.tsx +159 -6
  7. package/src/components/StudioHeader.tsx +20 -7
  8. package/src/components/StudioPreviewArea.tsx +6 -1
  9. package/src/components/StudioRightPanel.tsx +13 -0
  10. package/src/components/StudioToast.tsx +47 -7
  11. package/src/components/TimelineToolbar.tsx +12 -122
  12. package/src/components/editor/AnimationCard.tsx +64 -10
  13. package/src/components/editor/ArcPathControls.tsx +131 -0
  14. package/src/components/editor/BorderRadiusEditor.tsx +209 -0
  15. package/src/components/editor/DomEditOverlay.tsx +70 -11
  16. package/src/components/editor/DopesheetStrip.tsx +141 -0
  17. package/src/components/editor/EaseCurveSection.tsx +82 -7
  18. package/src/components/editor/GestureTrailOverlay.tsx +132 -0
  19. package/src/components/editor/GsapAnimationSection.tsx +14 -1
  20. package/src/components/editor/KeyframeDiamond.tsx +27 -12
  21. package/src/components/editor/LayersPanel.tsx +14 -12
  22. package/src/components/editor/MotionPathOverlay.tsx +146 -0
  23. package/src/components/editor/PropertyPanel.tsx +196 -66
  24. package/src/components/editor/SourceEditor.tsx +0 -1
  25. package/src/components/editor/StaggerControls.tsx +61 -0
  26. package/src/components/editor/domEditOverlayGeometry.test.ts +13 -0
  27. package/src/components/editor/domEditOverlayGeometry.ts +2 -1
  28. package/src/components/editor/domEditing.test.ts +43 -0
  29. package/src/components/editor/domEditing.ts +2 -0
  30. package/src/components/editor/domEditingElement.ts +25 -2
  31. package/src/components/editor/domEditingLayers.test.ts +78 -0
  32. package/src/components/editor/domEditingLayers.ts +33 -13
  33. package/src/components/editor/domEditingTypes.ts +1 -0
  34. package/src/components/editor/manualEditingAvailability.ts +1 -1
  35. package/src/components/editor/manualEdits.ts +3 -0
  36. package/src/components/editor/manualEditsDom.ts +23 -5
  37. package/src/components/editor/manualOffsetDrag.ts +59 -0
  38. package/src/components/editor/panelTokens.ts +10 -0
  39. package/src/components/editor/propertyPanelColor.tsx +2 -2
  40. package/src/components/editor/propertyPanelFill.tsx +1 -1
  41. package/src/components/editor/propertyPanelHelpers.ts +18 -2
  42. package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
  43. package/src/components/editor/propertyPanelPrimitives.tsx +38 -25
  44. package/src/components/editor/propertyPanelSections.tsx +4 -6
  45. package/src/components/editor/propertyPanelStyleSections.tsx +30 -8
  46. package/src/components/editor/useDomEditOverlayRects.ts +46 -2
  47. package/src/components/renders/RenderQueue.tsx +121 -100
  48. package/src/components/renders/RenderQueueItem.tsx +13 -13
  49. package/src/contexts/DomEditContext.tsx +12 -0
  50. package/src/contexts/FileManagerContext.tsx +3 -0
  51. package/src/contexts/StudioContext.tsx +0 -4
  52. package/src/hooks/gsapKeyframeCommit.ts +92 -0
  53. package/src/hooks/gsapRuntimeBridge.ts +147 -85
  54. package/src/hooks/gsapRuntimeKeyframes.ts +75 -24
  55. package/src/hooks/gsapRuntimePreview.ts +19 -0
  56. package/src/hooks/useAppHotkeys.ts +18 -0
  57. package/src/hooks/useAskAgentModal.ts +2 -4
  58. package/src/hooks/useDomEditCommits.ts +11 -17
  59. package/src/hooks/useDomEditSession.ts +47 -4
  60. package/src/hooks/useEnableKeyframes.ts +171 -0
  61. package/src/hooks/useFileManager.ts +7 -0
  62. package/src/hooks/useGestureRecording.ts +340 -0
  63. package/src/hooks/useGsapScriptCommits.ts +171 -35
  64. package/src/hooks/useGsapSelectionHandlers.ts +27 -8
  65. package/src/hooks/useGsapTweenCache.ts +169 -11
  66. package/src/hooks/useKeyframeKeyboard.ts +103 -0
  67. package/src/hooks/useStudioContextValue.ts +5 -4
  68. package/src/hooks/useStudioUrlState.ts +1 -2
  69. package/src/hooks/useTimelineEditing.ts +50 -3
  70. package/src/hooks/useToast.ts +6 -1
  71. package/src/player/components/ShortcutsPanel.tsx +40 -0
  72. package/src/player/components/TimelineClipDiamonds.tsx +3 -3
  73. package/src/player/components/TimelinePropertyRows.tsx +120 -0
  74. package/src/player/lib/timelineDOM.test.ts +55 -0
  75. package/src/player/lib/timelineDOM.ts +13 -0
  76. package/src/player/lib/timelineIframeHelpers.test.ts +51 -0
  77. package/src/player/lib/timelineIframeHelpers.ts +1 -0
  78. package/src/player/store/playerStore.ts +43 -0
  79. package/src/utils/audioBeatDetection.ts +58 -0
  80. package/src/utils/globalTimeCompiler.test.ts +169 -0
  81. package/src/utils/globalTimeCompiler.ts +77 -0
  82. package/src/utils/gsapSoftReload.ts +30 -10
  83. package/src/utils/keyframeSnapping.test.ts +74 -0
  84. package/src/utils/keyframeSnapping.ts +63 -0
  85. package/src/utils/rdpSimplify.ts +183 -0
  86. package/src/utils/sourcePatcher.ts +2 -0
  87. package/dist/assets/index-DHcptK1_.css +0 -1
  88. package/dist/assets/index-DtSCUvYQ.js +0 -140
@@ -0,0 +1 @@
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}.pointer-events-auto{pointer-events:auto}.\!visible{visibility:visible!important}.visible{visibility:visible}.invisible{visibility:hidden}.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}.inset-2{top:.5rem;right:.5rem;bottom:.5rem;left:.5rem}.inset-x-0{left:0;right:0}.inset-y-0{top:0;bottom:0}.-bottom-1\.5{bottom:-.375rem}.-right-1\.5{right:-.375rem}.bottom-0{bottom:0}.bottom-1{bottom:.25rem}.bottom-2{bottom:.5rem}.bottom-3{bottom:.75rem}.bottom-6{bottom:1.5rem}.bottom-full{bottom:100%}.left-0{left:0}.left-1{left:.25rem}.left-1\/2{left:50%}.left-2{left:.5rem}.left-3{left:.75rem}.left-\[100px\]{left:100px}.left-\[176px\]{left:176px}.left-\[24px\]{left:24px}.left-\[31px\]{left:31px}.left-\[34px\]{left:34px}.left-\[52px\]{left:52px}.left-\[82px\]{left:82px}.left-full{left:100%}.right-0{right:0}.right-1{right:.25rem}.right-2{right:.5rem}.right-3{right:.75rem}.right-6{right:1.5rem}.top-0{top:0}.top-1{top:.25rem}.top-1\/2{top:50%}.top-2{top:.5rem}.top-3{top:.75rem}.top-\[18px\]{top:18px}.top-\[21px\]{top:21px}.top-\[27px\]{top:27px}.top-\[2px\]{top:2px}.top-\[3px\]{top:3px}.top-\[51px\]{top:51px}.top-\[calc\(100\%\+6px\)\]{top:calc(100% + 6px)}.top-full{top:100%}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[1\]{z-index:1}.z-\[200\]{z-index:200}.z-\[3\]{z-index:3}.z-\[4\]{z-index:4}.z-\[90\]{z-index:90}.z-\[91\]{z-index:91}.z-\[9999\]{z-index:9999}.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}.-mt-\[72px\]{margin-top:-72px}.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}.mb-4{margin-bottom:1rem}.ml-0\.5{margin-left:.125rem}.ml-1{margin-left:.25rem}.ml-1\.5{margin-left:.375rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-\[16px\]{margin-left:16px}.ml-auto{margin-left:auto}.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}.mt-\[22px\]{margin-top:22px}.\!block{display:block!important}.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}.aspect-video{aspect-ratio:16 / 9}.h-0\.5{height:.125rem}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-2{height:.5rem}.h-24{height:6rem}.h-28{height:7rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-32{height:8rem}.h-36{height:9rem}.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-\[18px\]{height:18px}.h-\[3px\]{height:3px}.h-\[45px\]{height:45px}.h-\[52px\]{height:52px}.h-\[5px\]{height:5px}.h-\[70px\]{height:70px}.h-full{height:100%}.h-px{height:1px}.max-h-24{max-height:6rem}.max-h-40{max-height:10rem}.max-h-64{max-height:16rem}.max-h-\[300px\]{max-height:300px}.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}.min-h-\[240px\]{min-height:240px}.w-0{width:0px}.w-1\.5{width:.375rem}.w-10{width:2.5rem}.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-6{width:1.5rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-\[110px\]{width:110px}.w-\[118px\]{width:118px}.w-\[160px\]{width:160px}.w-\[292px\]{width:292px}.w-\[320px\]{width:320px}.w-\[480px\]{width:480px}.w-\[560px\]{width:560px}.w-\[56px\]{width:56px}.w-\[72px\]{width:72px}.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-\[180px\]{min-width:180px}.min-w-\[20px\]{min-width:20px}.min-w-\[220px\]{min-width:220px}.min-w-\[36px\]{min-width:36px}.min-w-\[44px\]{min-width:44px}.min-w-\[56px\]{min-width:56px}.min-w-\[58px\]{min-width:58px}.min-w-\[96px\]{min-width:96px}.max-w-\[260px\]{max-width:260px}.max-w-\[280px\]{max-width:280px}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-full{max-width:100%}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow,.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))}.translate-y-0{--tw-translate-y: 0px;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-2{--tw-translate-y: .5rem;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))}.-rotate-90{--tw-rotate: -90deg;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))}.rotate-90{--tw-rotate: 90deg;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))!important}.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-ew-resize{cursor:ew-resize}.cursor-grab{cursor:grab}.cursor-grabbing{cursor:grabbing}.cursor-help{cursor:help}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.touch-none{touch-action:none}.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}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-\[minmax\(0\,1fr\)_68px_28px\]{grid-template-columns:minmax(0,1fr) 68px 28px}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.grid-cols-\[minmax\(0\,1fr\)_auto_auto\]{grid-template-columns:minmax(0,1fr) auto auto}.grid-cols-\[repeat\(auto-fit\,minmax\(118px\,1fr\)\)\]{grid-template-columns:repeat(auto-fit,minmax(118px,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}.gap-4{gap:1rem}.gap-\[2px\]{gap:2px}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-y-1{row-gap:.25rem}.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))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.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}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-\[10px\]{border-radius:10px}.rounded-\[11px\]{border-radius:11px}.rounded-\[14px\]{border-radius:14px}.rounded-\[18px\]{border-radius:18px}.rounded-\[9px\]{border-radius:9px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.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-panel-border{--tw-border-opacity: 1;border-color:rgb(30 30 30 / var(--tw-border-opacity, 1))}.border-purple-500\/20{border-color:#a855f733}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-red-500\/30{border-color:#ef44444d}.border-studio-accent{--tw-border-opacity: 1;border-color:rgb(60 230 172 / var(--tw-border-opacity, 1))}.border-studio-accent\/25{border-color:#3ce6ac40}.border-studio-accent\/30{border-color:#3ce6ac4d}.border-studio-accent\/40{border-color:#3ce6ac66}.border-studio-accent\/50{border-color:#3ce6ac80}.border-studio-accent\/60{border-color:#3ce6ac99}.border-studio-accent\/70{border-color:#3ce6acb3}.border-studio-accent\/80{border-color:#3ce6accc}.border-transparent{border-color:transparent}.border-white{--tw-border-opacity: 1;border-color:rgb(255 255 255 / var(--tw-border-opacity, 1))}.border-white\/10{border-color:#ffffff1a}.border-white\/20{border-color:#fff3}.border-white\/90{border-color:#ffffffe6}.border-yellow-400\/30{border-color:#facc154d}.border-yellow-400\/40{border-color:#facc1566}.border-t-neutral-500{--tw-border-opacity: 1;border-top-color:rgb(115 115 115 / var(--tw-border-opacity, 1))}.bg-\[\#0a0a0b\]{--tw-bg-opacity: 1;background-color:rgb(10 10 11 / var(--tw-bg-opacity, 1))}.bg-\[\#0d1117\]{--tw-bg-opacity: 1;background-color:rgb(13 17 23 / var(--tw-bg-opacity, 1))}.bg-\[\#0f141c\]{--tw-bg-opacity: 1;background-color:rgb(15 20 28 / var(--tw-bg-opacity, 1))}.bg-\[\#3CE6AC\]\/10{background-color:#3ce6ac1a}.bg-\[\#3CE6AC\]\/5{background-color:#3ce6ac0d}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-amber-500\/15{background-color:#f59e0b26}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/40{background-color:#0006}.bg-black\/50{background-color:#00000080}.bg-black\/60{background-color:#0009}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500\/15{background-color:#3b82f626}.bg-cyan-400{--tw-bg-opacity: 1;background-color:rgb(34 211 238 / var(--tw-bg-opacity, 1))}.bg-cyan-500\/15{background-color:#06b6d426}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500\/15{background-color:#22c55e26}.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\/50{background-color:#26262680}.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-900\/60{background-color:#17171799}.bg-neutral-900\/80{background-color:#171717cc}.bg-neutral-900\/95{background-color:#171717f2}.bg-neutral-950{--tw-bg-opacity: 1;background-color:rgb(10 10 10 / var(--tw-bg-opacity, 1))}.bg-neutral-950\/80{background-color:#0a0a0acc}.bg-panel-accent{--tw-bg-opacity: 1;background-color:rgb(60 230 172 / var(--tw-bg-opacity, 1))}.bg-panel-accent\/10{background-color:#3ce6ac1a}.bg-panel-accent\/40{background-color:#3ce6ac66}.bg-panel-bg{--tw-bg-opacity: 1;background-color:rgb(12 12 14 / var(--tw-bg-opacity, 1))}.bg-panel-border{--tw-bg-opacity: 1;background-color:rgb(30 30 30 / var(--tw-bg-opacity, 1))}.bg-panel-hover{--tw-bg-opacity: 1;background-color:rgb(39 39 42 / var(--tw-bg-opacity, 1))}.bg-panel-input{--tw-bg-opacity: 1;background-color:rgb(22 22 24 / var(--tw-bg-opacity, 1))}.bg-pink-400{--tw-bg-opacity: 1;background-color:rgb(244 114 182 / var(--tw-bg-opacity, 1))}.bg-pink-500\/15{background-color:#ec489926}.bg-purple-400{--tw-bg-opacity: 1;background-color:rgb(192 132 252 / var(--tw-bg-opacity, 1))}.bg-purple-500\/10{background-color:#a855f71a}.bg-purple-500\/15{background-color:#a855f726}.bg-purple-900\/70{background-color:#581c87b3}.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-500\/15{background-color:#ef444426}.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-950\/30{background-color:#450a0a4d}.bg-rose-400{--tw-bg-opacity: 1;background-color:rgb(251 113 133 / var(--tw-bg-opacity, 1))}.bg-rose-500\/15{background-color:#f43f5e26}.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\/5{background-color:#3ce6ac0d}.bg-studio-accent\/60{background-color:#3ce6ac99}.bg-studio-accent\/90{background-color:#3ce6ace6}.bg-studio-accent\/\[0\.03\]{background-color:#3ce6ac08}.bg-studio-accent\/\[0\.04\]{background-color:#3ce6ac0a}.bg-studio-accent\/\[0\.05\]{background-color:#3ce6ac0d}.bg-studio-accent\/\[0\.06\]{background-color:#3ce6ac0f}.bg-transparent{background-color:transparent}.bg-violet-400{--tw-bg-opacity: 1;background-color:rgb(167 139 250 / var(--tw-bg-opacity, 1))}.bg-violet-500\/15{background-color:#8b5cf626}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-white\/10{background-color:#ffffff1a}.bg-white\/15{background-color:#ffffff26}.bg-white\/30{background-color:#ffffff4d}.bg-white\/5{background-color:#ffffff0d}.bg-white\/70{background-color:#ffffffb3}.bg-white\/\[0\.035\]{background-color:#ffffff09}.bg-white\/\[0\.04\]{background-color:#ffffff0a}.bg-white\/\[0\.07\]{background-color:#ffffff12}.bg-yellow-400\/10{background-color:#facc151a}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.from-black{--tw-gradient-from: #000 var(--tw-gradient-from-position);--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-white{--tw-gradient-from: #fff var(--tw-gradient-from-position);--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.to-transparent{--tw-gradient-to: transparent var(--tw-gradient-to-position)}.fill-neutral-400{fill:#a3a3a3}.fill-neutral-500{fill:#737373}.fill-neutral-600{fill:#525252}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-\[2px\]{padding:2px}.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-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.py-\[5px\]{padding-top:5px;padding-bottom:5px}.py-\[7px\]{padding-top:7px;padding-bottom:7px}.py-px{padding-top:1px;padding-bottom:1px}.pb-0\.5{padding-bottom:.125rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-2\.5{padding-bottom:.625rem}.pb-3{padding-bottom:.75rem}.pl-1{padding-left:.25rem}.pl-4{padding-left:1rem}.pl-6{padding-left:1.5rem}.pl-7{padding-left:1.75rem}.pr-1{padding-right:.25rem}.pr-2{padding-right:.5rem}.pr-9{padding-right:2.25rem}.pt-0\.5{padding-top:.125rem}.pt-1{padding-top:.25rem}.pt-1\.5{padding-top:.375rem}.pt-2{padding-top:.5rem}.pt-2\.5{padding-top:.625rem}.pt-3{padding-top:.75rem}.pt-\[72px\]{padding-top:72px}.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-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[7px\]{font-size:7px}.text-\[8px\]{font-size:8px}.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-bold{font-weight:700}.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-4{line-height:1rem}.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\.14em\]{letter-spacing:.14em}.tracking-\[0\.16em\]{letter-spacing:.16em}.tracking-\[0\.18em\]{letter-spacing:.18em}.tracking-\[0\.22em\]{letter-spacing:.22em}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-\[\#09090B\]{--tw-text-opacity: 1;color:rgb(9 9 11 / var(--tw-text-opacity, 1))}.text-\[\#7f8796\]{--tw-text-opacity: 1;color:rgb(127 135 150 / var(--tw-text-opacity, 1))}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / 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-50{--tw-text-opacity: 1;color:rgb(250 250 250 / 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-orange-400\/70{color:#fb923cb3}.text-panel-accent{--tw-text-opacity: 1;color:rgb(60 230 172 / var(--tw-text-opacity, 1))}.text-panel-accent\/70{color:#3ce6acb3}.text-panel-text-1{--tw-text-opacity: 1;color:rgb(228 228 231 / var(--tw-text-opacity, 1))}.text-panel-text-2{--tw-text-opacity: 1;color:rgb(161 161 170 / var(--tw-text-opacity, 1))}.text-panel-text-3{--tw-text-opacity: 1;color:rgb(113 113 122 / var(--tw-text-opacity, 1))}.text-panel-text-4{--tw-text-opacity: 1;color:rgb(82 82 91 / var(--tw-text-opacity, 1))}.text-panel-text-5{--tw-text-opacity: 1;color:rgb(63 63 70 / var(--tw-text-opacity, 1))}.text-pink-400{--tw-text-opacity: 1;color:rgb(244 114 182 / var(--tw-text-opacity, 1))}.text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / 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-rose-400{--tw-text-opacity: 1;color:rgb(251 113 133 / 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-violet-400{--tw-text-opacity: 1;color:rgb(167 139 250 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-white\/60{color:#fff9}.text-white\/80{color:#fffc}.text-white\/90{color:#ffffffe6}.text-yellow-300{--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.line-through{text-decoration-line:line-through}.placeholder-neutral-600::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(82 82 82 / var(--tw-placeholder-opacity, 1))}.placeholder-neutral-600::placeholder{--tw-placeholder-opacity: 1;color:rgb(82 82 82 / var(--tw-placeholder-opacity, 1))}.accent-studio-accent{accent-color:#3CE6AC}.accent-yellow-400{accent-color:#facc15}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-25{opacity:.25}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.mix-blend-difference{mix-blend-mode:difference}.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-\[0_0_0_1px_rgba\(0\,0\,0\,0\.35\)\]{--tw-shadow: 0 0 0 1px rgba(0,0,0,.35);--tw-shadow-colored: 0 0 0 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-\[0_0_0_1px_rgba\(0\,0\,0\,0\.45\)\]{--tw-shadow: 0 0 0 1px rgba(0,0,0,.45);--tw-shadow-colored: 0 0 0 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-\[0_0_0_1px_rgba\(0\,0\,0\,0\.85\)\,0_6px_14px_rgba\(0\,0\,0\,0\.5\)\]{--tw-shadow: 0 0 0 1px rgba(0,0,0,.85),0 6px 14px rgba(0,0,0,.5);--tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color), 0 6px 14px 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-\[0_0_0_1px_rgba\(0\,0\,0\,0\.85\)\,0_8px_18px_rgba\(0\,0\,0\,0\.45\)\]{--tw-shadow: 0 0 0 1px rgba(0,0,0,.85),0 8px 18px rgba(0,0,0,.45);--tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color), 0 8px 18px 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-\[0_0_0_1px_rgba\(60\,230\,172\,0\.25\)\]{--tw-shadow: 0 0 0 1px rgba(60,230,172,.25);--tw-shadow-colored: 0 0 0 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-\[0_0_0_1px_rgba\(60\,230\,172\,0\.3\)\]{--tw-shadow: 0 0 0 1px rgba(60,230,172,.3);--tw-shadow-colored: 0 0 0 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-\[0_0_0_2px_rgba\(60\,230\,172\,0\.18\)\]{--tw-shadow: 0 0 0 2px rgba(60,230,172,.18);--tw-shadow-colored: 0 0 0 2px 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-\[0_18px_40px_rgba\(0\,0\,0\,0\.3\)\,0_4px_14px_rgba\(0\,0\,0\,0\.18\)\]{--tw-shadow: 0 18px 40px rgba(0,0,0,.3),0 4px 14px rgba(0,0,0,.18);--tw-shadow-colored: 0 18px 40px var(--tw-shadow-color), 0 4px 14px 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-\[0_1px_2px_rgba\(0\,0\,0\,0\.2\)\]{--tw-shadow: 0 1px 2px rgba(0,0,0,.2);--tw-shadow-colored: 0 1px 2px 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-\[inset_0_0_0_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow: inset 0 0 0 1px rgba(255,255,255,.06);--tw-shadow-colored: inset 0 0 0 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-\[inset_0_0_0_2px_rgba\(60\,230\,172\,0\.6\)\]{--tw-shadow: inset 0 0 0 2px rgba(60,230,172,.6);--tw-shadow-colored: inset 0 0 0 2px 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-\[inset_0_1px_0_rgba\(255\,255\,255\,0\.03\)\]{--tw-shadow: inset 0 1px 0 rgba(255,255,255,.03);--tw-shadow-colored: inset 0 1px 0 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-\[inset_0_1px_0_rgba\(255\,255\,255\,0\.04\)\]{--tw-shadow: inset 0 1px 0 rgba(255,255,255,.04);--tw-shadow-colored: inset 0 1px 0 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-\[inset_0_1px_0_rgba\(255\,255\,255\,0\.08\)\]{--tw-shadow: inset 0 1px 0 rgba(255,255,255,.08);--tw-shadow-colored: inset 0 1px 0 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-\[inset_0_1px_2px_rgba\(0\,0\,0\,0\.55\)\]{--tw-shadow: inset 0 1px 2px rgba(0,0,0,.55);--tw-shadow-colored: inset 0 1px 2px 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-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 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)}.shadow-black\/50{--tw-shadow-color: rgb(0 0 0 / .5);--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-inset{--tw-ring-inset: inset}.ring-panel-accent\/30{--tw-ring-color: rgb(60 230 172 / .3)}.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)}.grayscale{--tw-grayscale: grayscale(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)}.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)}.backdrop-blur-xl{--tw-backdrop-blur: blur(24px);-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)}.backdrop-filter{-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)}:root{color-scheme:dark}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}.hf-loader{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;width:min(34rem,100%);padding:1.5rem;box-sizing:border-box;text-align:center;cursor:default;-moz-user-select:none;user-select:none;-webkit-user-select:none;-webkit-user-drag:none}.hf-frame{display:grid;place-items:center;width:100%;height:100%;min-height:12rem;border:1px solid rgba(255,255,255,.08);background:#00000085}.hf-loader-mark-frame{display:grid;place-items:center;overflow:visible;transform-origin:50% 50%;-moz-user-select:none;user-select:none;-webkit-user-select:none;-webkit-user-drag:none}.hf-loader-mark{display:block;overflow:visible;filter:drop-shadow(0 0 7px rgba(79,219,94,.2));-moz-user-select:none;user-select:none;-webkit-user-select:none;-webkit-user-drag:none}.hf-loader-title{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:1rem;font-weight:600;letter-spacing:0;color:var(--hf-heading, #f4f4f5);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hf-loader-detail{max-width:32rem;min-height:2.5rem;overflow:hidden;color:var(--hf-text-secondary, rgba(244, 244, 245, .68));font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:.82rem;line-height:1.6}.hf-loader-mono{width:min(36rem,100%);min-height:1.5rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--hf-text-tertiary, rgba(244, 244, 245, .46));font-family:IBM Plex Mono,SF Mono,Fira Code,monospace;font-size:.75rem;letter-spacing:0;font-variant-numeric:tabular-nums}.hf-loader-progress{width:min(18rem,72vw);height:.375rem;overflow:hidden;border-radius:999px;background:#ffffff1a}@keyframes hf-thumb-fade{0%{opacity:0}to{opacity:1}}.hf-loader-progress__fill{width:100%;height:100%;transform:scaleX(0);transform-origin:left center;border-radius:inherit;background:linear-gradient(90deg,#06e3fa,#4fdb5e);transition:transform .16s ease}.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}.focus-within\:border-neutral-600:focus-within{--tw-border-opacity: 1;border-color:rgb(82 82 82 / var(--tw-border-opacity, 1))}.focus-within\:ring-1:focus-within{--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)}.focus-within\:ring-panel-accent\/30:focus-within{--tw-ring-color: rgb(60 230 172 / .3)}.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-neutral-800:hover{--tw-border-opacity: 1;border-color:rgb(38 38 38 / var(--tw-border-opacity, 1))}.hover\:border-studio-accent\/50:hover{border-color:#3ce6ac80}.hover\:bg-black\/60:hover{background-color:#0009}.hover\:bg-black\/70:hover{background-color:#000000b3}.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-700\/50:hover{background-color:#40404080}.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-neutral-800\/70:hover{background-color:#262626b3}.hover\:bg-neutral-900:hover{--tw-bg-opacity: 1;background-color:rgb(23 23 23 / var(--tw-bg-opacity, 1))}.hover\:bg-panel-accent\/10:hover{background-color:#3ce6ac1a}.hover\:bg-panel-accent\/20:hover{background-color:#3ce6ac33}.hover\:bg-panel-hover:hover{--tw-bg-opacity: 1;background-color:rgb(39 39 42 / var(--tw-bg-opacity, 1))}.hover\:bg-panel-hover\/30:hover{background-color:#27272a4d}.hover\:bg-panel-hover\/40:hover{background-color:#27272a66}.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:hover{--tw-bg-opacity: 1;background-color:rgb(60 230 172 / var(--tw-bg-opacity, 1))}.hover\:bg-studio-accent\/25:hover{background-color:#3ce6ac40}.hover\:bg-studio-accent\/80:hover{background-color:#3ce6accc}.hover\:bg-white\/10:hover{background-color:#ffffff1a}.hover\:bg-white\/25:hover{background-color:#ffffff40}.hover\:bg-white\/\[0\.06\]:hover{background-color:#ffffff0f}.hover\:text-amber-300:hover{--tw-text-opacity: 1;color:rgb(252 211 77 / 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-orange-300:hover{--tw-text-opacity: 1;color:rgb(253 186 116 / var(--tw-text-opacity, 1))}.hover\:text-panel-accent:hover{--tw-text-opacity: 1;color:rgb(60 230 172 / var(--tw-text-opacity, 1))}.hover\:text-panel-text-1:hover{--tw-text-opacity: 1;color:rgb(228 228 231 / var(--tw-text-opacity, 1))}.hover\:text-panel-text-2:hover{--tw-text-opacity: 1;color:rgb(161 161 170 / var(--tw-text-opacity, 1))}.hover\:text-panel-text-3:hover{--tw-text-opacity: 1;color:rgb(113 113 122 / var(--tw-text-opacity, 1))}.hover\:text-red-300:hover{--tw-text-opacity: 1;color:rgb(252 165 165 / 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\:text-white\/80:hover{color:#fffc}.hover\:opacity-100:hover{opacity:1}.hover\:opacity-80:hover{opacity:.8}.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-neutral-700:focus{--tw-border-opacity: 1;border-color:rgb(64 64 64 / var(--tw-border-opacity, 1))}.focus\:border-panel-accent:focus{--tw-border-opacity: 1;border-color:rgb(60 230 172 / 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\:border-studio-accent\/60:focus{border-color:#3ce6ac99}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--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)}.focus\:ring-2:focus{--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)}.focus\:ring-panel-accent:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(60 230 172 / var(--tw-ring-opacity, 1))}.focus\:ring-panel-accent\/40:focus{--tw-ring-color: rgb(60 230 172 / .4)}.focus\:ring-studio-accent\/30:focus{--tw-ring-color: rgb(60 230 172 / .3)}.focus\:ring-studio-accent\/40:focus{--tw-ring-color: rgb(60 230 172 / .4)}.focus-visible\:rounded:focus-visible{border-radius:.25rem}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-1:focus-visible{--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)}.focus-visible\:ring-2:focus-visible{--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)}.focus-visible\:ring-studio-accent\/50:focus-visible{--tw-ring-color: rgb(60 230 172 / .5)}.focus-visible\:ring-white\/30:focus-visible{--tw-ring-color: rgb(255 255 255 / .3)}.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))}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-default:disabled{cursor:default}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:border-neutral-800:disabled{--tw-border-opacity: 1;border-color:rgb(38 38 38 / var(--tw-border-opacity, 1))}.disabled\:text-neutral-600:disabled{--tw-text-opacity: 1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.disabled\:text-neutral-700:disabled{--tw-text-opacity: 1;color:rgb(64 64 64 / var(--tw-text-opacity, 1))}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:block{display:block}.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))}.group\/card:hover .group-hover\/card\:opacity-100{opacity:1}@media(min-width:640px){.sm\:inline{display:inline}}.\[\&\:\:-webkit-slider-runnable-track\]\:h-\[2px\]::-webkit-slider-runnable-track{height:2px}.\[\&\:\:-webkit-slider-runnable-track\]\:rounded-full::-webkit-slider-runnable-track{border-radius:9999px}.\[\&\:\:-webkit-slider-runnable-track\]\:bg-panel-border::-webkit-slider-runnable-track{--tw-bg-opacity: 1;background-color:rgb(30 30 30 / var(--tw-bg-opacity, 1))}.\[\&\:\:-webkit-slider-thumb\:active\]\:cursor-grabbing::-webkit-slider-thumb:active{cursor:grabbing}.\[\&\:\:-webkit-slider-thumb\]\:-mt-1::-webkit-slider-thumb{margin-top:-.25rem}.\[\&\:\:-webkit-slider-thumb\]\:h-3::-webkit-slider-thumb{height:.75rem}.\[\&\:\:-webkit-slider-thumb\]\:h-\[10px\]::-webkit-slider-thumb{height:10px}.\[\&\:\:-webkit-slider-thumb\]\:w-3::-webkit-slider-thumb{width:.75rem}.\[\&\:\:-webkit-slider-thumb\]\:w-\[10px\]::-webkit-slider-thumb{width:10px}.\[\&\:\:-webkit-slider-thumb\]\:cursor-grab::-webkit-slider-thumb{cursor:grab}.\[\&\:\:-webkit-slider-thumb\]\:appearance-none::-webkit-slider-thumb{-webkit-appearance:none;-moz-appearance:none;appearance:none}.\[\&\:\:-webkit-slider-thumb\]\:rounded-full::-webkit-slider-thumb{border-radius:9999px}.\[\&\:\:-webkit-slider-thumb\]\:bg-white::-webkit-slider-thumb{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.\[\&\:\:-webkit-slider-thumb\]\:bg-yellow-400::-webkit-slider-thumb{--tw-bg-opacity: 1;background-color:rgb(250 204 21 / var(--tw-bg-opacity, 1))}.\[\&\:\:-webkit-slider-thumb\]\:shadow-\[0_0_0_2px_\#0C0C0E\,0_1px_3px_rgba\(0\,0\,0\,0\.5\)\]::-webkit-slider-thumb{--tw-shadow: 0 0 0 2px #0C0C0E,0 1px 3px rgba(0,0,0,.5);--tw-shadow-colored: 0 0 0 2px var(--tw-shadow-color), 0 1px 3px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}
package/dist/index.html CHANGED
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <title>HyperFrames Studio</title>
8
- <script type="module" crossorigin src="/assets/index-DtSCUvYQ.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-DHcptK1_.css">
8
+ <script type="module" crossorigin src="/assets/index-BA19FAPN.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-CGlIm_-E.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.6.85",
3
+ "version": "0.6.87",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,8 +31,8 @@
31
31
  "@codemirror/view": "6.40.0",
32
32
  "@phosphor-icons/react": "^2.1.10",
33
33
  "mediabunny": "^1.45.3",
34
- "@hyperframes/core": "0.6.85",
35
- "@hyperframes/player": "0.6.85"
34
+ "@hyperframes/core": "0.6.87",
35
+ "@hyperframes/player": "0.6.87"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/react": "19",
@@ -46,7 +46,7 @@
46
46
  "vite": "^6.4.2",
47
47
  "vitest": "^3.2.4",
48
48
  "zustand": "^5.0.0",
49
- "@hyperframes/producer": "0.6.85"
49
+ "@hyperframes/producer": "0.6.87"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": "19",
package/src/App.tsx CHANGED
@@ -35,6 +35,10 @@ import type { DomEditSelection } from "./components/editor/domEditing";
35
35
  import { AskAgentModal } from "./components/AskAgentModal";
36
36
  import { StudioGlobalDragOverlay } from "./components/StudioGlobalDragOverlay";
37
37
  import { StudioHeader } from "./components/StudioHeader";
38
+ import { useGestureRecording } from "./hooks/useGestureRecording";
39
+ import { simplifyGestureSamples } from "./utils/rdpSimplify";
40
+
41
+ import { GestureTrailOverlay } from "./components/editor/GestureTrailOverlay";
38
42
  import { StudioLeftSidebar } from "./components/StudioLeftSidebar";
39
43
  import { StudioPreviewArea } from "./components/StudioPreviewArea";
40
44
  import { StudioRightPanel } from "./components/StudioRightPanel";
@@ -94,7 +98,6 @@ export function StudioApp() {
94
98
  const captionEditMode = useCaptionStore((s) => s.isEditMode);
95
99
  const captionHasSelection = useCaptionStore((s) => s.selectedSegmentIds.size > 0);
96
100
  const captionSync = useCaptionSync(projectId);
97
- const currentTime = usePlayerStore((s) => s.currentTime);
98
101
  const timelineElements = usePlayerStore((s) => s.elements);
99
102
  const setSelectedTimelineElementId = usePlayerStore((s) => s.setSelectedElementId);
100
103
  const timelineDuration = usePlayerStore((s) => s.duration);
@@ -128,7 +131,7 @@ export function StudioApp() {
128
131
  return !v;
129
132
  });
130
133
  }, []);
131
- const { appToast, showToast } = useToast();
134
+ const { appToast, showToast, dismissToast } = useToast();
132
135
  const panelLayout = usePanelLayout({
133
136
  rightCollapsed: initialUrlStateRef.current.rightCollapsed,
134
137
  rightPanelTab: initialUrlStateRef.current.rightPanelTab,
@@ -136,6 +139,7 @@ export function StudioApp() {
136
139
  const editHistory = usePersistentEditHistory({ projectId });
137
140
  const domEditSaveTimestampRef = useRef(0);
138
141
  const pendingTimelineEditPathRef = useRef(new Set<string>());
142
+ const isGestureRecordingRef = useRef(false);
139
143
  const reloadPreview = useCallback(() => {
140
144
  setRefreshKey((k) => k + 1);
141
145
  }, []);
@@ -185,6 +189,7 @@ export function StudioApp() {
185
189
  previewIframeRef,
186
190
  pendingTimelineEditPathRef,
187
191
  uploadProjectFiles: fileManager.uploadProjectFiles,
192
+ isRecordingRef: isGestureRecordingRef,
188
193
  });
189
194
 
190
195
  const blockCtx = useMemo(
@@ -306,6 +311,7 @@ export function StudioApp() {
306
311
  onResetKeyframes: () => resetKeyframesRef.current(),
307
312
  onDeleteSelectedKeyframes: () => deleteSelectedKeyframesRef.current(),
308
313
  onAfterUndoRedo: () => invalidateGsapCacheRef.current(),
314
+ onToggleRecording: () => handleToggleRecordingRef.current(),
309
315
  });
310
316
  const selectSidebarTabStable = useCallback(
311
317
  (tab: SidebarTab) => leftSidebarRef.current?.selectTab(tab),
@@ -325,7 +331,6 @@ export function StudioApp() {
325
331
  compositionLoading,
326
332
  previewIframeRef,
327
333
  timelineElements,
328
- currentTime,
329
334
  setSelectedTimelineElementId,
330
335
  setRightCollapsed: panelLayout.setRightCollapsed,
331
336
  setRightPanelTab: panelLayout.setRightPanelTab,
@@ -334,6 +339,7 @@ export function StudioApp() {
334
339
  queueDomEditSave: previewPersistence.queueDomEditSave,
335
340
  readProjectFile: fileManager.readProjectFile,
336
341
  writeProjectFile: fileManager.writeProjectFile,
342
+ updateEditingFileContent: fileManager.updateEditingFileContent,
337
343
  domEditSaveTimestampRef,
338
344
  editHistory: { recordEdit: editHistory.recordEdit },
339
345
  fileTree: fileManager.fileTree,
@@ -399,6 +405,128 @@ export function StudioApp() {
399
405
 
400
406
  const dragOverlay = useDragOverlay(fileManager.handleImportFiles);
401
407
 
408
+ // Gesture recording
409
+ const gestureRecording = useGestureRecording();
410
+ const [gestureState, setGestureState] = useState<"idle" | "recording">("idle");
411
+ // Synchronous mirror of gestureState — immune to React batching.
412
+ // Prevents double-R-press within a single render cycle from swallowing the stop.
413
+ const gestureStateRef = useRef<"idle" | "recording">("idle");
414
+ const recordingAutoStopRef = useRef<ReturnType<typeof setInterval>>(undefined);
415
+ const recordingStartTimeRef = useRef(0);
416
+ const commitInFlightRef = useRef(false);
417
+ const handleToggleRecordingRef = useRef<() => void>(() => {});
418
+ const domEditSessionRef = useRef(domEditSession);
419
+ domEditSessionRef.current = domEditSession;
420
+
421
+ // Unmount: clear auto-stop interval
422
+ useEffect(() => () => clearInterval(recordingAutoStopRef.current), []);
423
+
424
+ // fallow-ignore-next-line complexity
425
+ const stopAndCommitRecording = useCallback(async () => {
426
+ clearInterval(recordingAutoStopRef.current);
427
+ if (commitInFlightRef.current) return;
428
+ commitInFlightRef.current = true;
429
+ gestureStateRef.current = "idle";
430
+ isGestureRecordingRef.current = false;
431
+ const frozenSamples = gestureRecording.stopRecording();
432
+ const store = usePlayerStore.getState();
433
+ store.setIsPlaying(false);
434
+ try {
435
+ const liveSession = domEditSessionRef.current;
436
+ const sel = liveSession.domEditSelection;
437
+ if (!sel) {
438
+ if (frozenSamples.length > 2) {
439
+ showToast("Selection lost during recording", "error");
440
+ }
441
+ return;
442
+ }
443
+ const duration = frozenSamples.length > 0 ? frozenSamples[frozenSamples.length - 1]!.time : 0;
444
+
445
+ if (frozenSamples.length <= 2) {
446
+ showToast("No gesture detected — move the pointer while recording", "error");
447
+ return;
448
+ }
449
+ if (duration <= 0) {
450
+ showToast("Recording too short — try again", "error");
451
+ return;
452
+ }
453
+
454
+ const simplified = simplifyGestureSamples(frozenSamples, duration, 5);
455
+ const sortedPcts = Array.from(simplified.keys()).sort((a, b) => a - b);
456
+
457
+ // Always create a new tween scoped to the recording range.
458
+ // Injecting into an existing tween creates keyframes before the recording
459
+ // start (from the convert-to-keyframes step), causing wrong positions.
460
+ const selector = sel.id ? `#${sel.id}` : sel.selector;
461
+ if (!selector) {
462
+ showToast("Cannot save — element has no selector", "error");
463
+ return;
464
+ }
465
+ if (liveSession.commitMutation) {
466
+ const recStart = recordingStartTimeRef.current;
467
+ const keyframes = sortedPcts.map((pct) => ({
468
+ percentage: pct,
469
+ properties: simplified.get(pct) as Record<string, number | string>,
470
+ }));
471
+
472
+ await liveSession.commitMutation(
473
+ {
474
+ type: "add-with-keyframes",
475
+ targetSelector: selector,
476
+ position: Math.round(recStart * 1000) / 1000,
477
+ duration: Math.round(duration * 1000) / 1000,
478
+ keyframes,
479
+ },
480
+ { label: "Gesture recording", softReload: true },
481
+ );
482
+ }
483
+ showToast(`Recorded ${sortedPcts.length} keyframes`, "info");
484
+ } finally {
485
+ store.requestSeek(recordingStartTimeRef.current);
486
+ gestureRecording.clearSamples();
487
+ setGestureState("idle");
488
+ commitInFlightRef.current = false;
489
+ }
490
+ }, [gestureRecording, showToast]);
491
+
492
+ const handleToggleRecording = useCallback(() => {
493
+ if (gestureStateRef.current === "recording") {
494
+ void stopAndCommitRecording();
495
+ return;
496
+ }
497
+ const sel = domEditSessionRef.current.domEditSelection;
498
+ if (!sel) {
499
+ showToast("Select an element first", "error");
500
+ return;
501
+ }
502
+ const iframe = previewIframeRef.current;
503
+ if (!iframe) {
504
+ showToast("Preview not ready — try again", "error");
505
+ return;
506
+ }
507
+
508
+ const store = usePlayerStore.getState();
509
+ recordingStartTimeRef.current = store.currentTime;
510
+ const elStart = Number.parseFloat(sel.dataAttributes?.start ?? "0") || 0;
511
+ const elDur = Number.parseFloat(sel.dataAttributes?.duration ?? "0") || 0;
512
+ const elementEnd = elDur > 0 ? elStart + elDur : undefined;
513
+ gestureRecording.startRecording(sel.element, iframe, elementEnd);
514
+ gestureStateRef.current = "recording";
515
+ isGestureRecordingRef.current = true;
516
+ setGestureState("recording");
517
+
518
+ clearInterval(recordingAutoStopRef.current);
519
+ const autoStopAt = elementEnd ?? Infinity;
520
+ recordingAutoStopRef.current = setInterval(() => {
521
+ const { currentTime: t, duration: d } = usePlayerStore.getState();
522
+ const limit = Math.min(autoStopAt, d);
523
+ if (limit > 0 && t >= limit - 0.05) {
524
+ void stopAndCommitRecording();
525
+ }
526
+ }, 100);
527
+ }, [gestureRecording, showToast, stopAndCommitRecording]);
528
+ handleToggleRecordingRef.current = handleToggleRecording;
529
+
402
530
  const handlePreviewIframeRef = useCallback(
403
531
  (iframe: HTMLIFrameElement | null) => {
404
532
  previewIframeRef.current = iframe;
@@ -434,12 +562,12 @@ export function StudioApp() {
434
562
  panelLayout.rightCollapsed,
435
563
  isPlaying,
436
564
  domEditSession.domEditSelection,
565
+ gestureState === "recording",
437
566
  );
438
567
 
439
568
  useStudioUrlState({
440
569
  projectId,
441
570
  activeCompPath,
442
- currentTime,
443
571
  duration: effectiveTimelineDuration,
444
572
  isPlaying,
445
573
  compositionLoading,
@@ -465,7 +593,6 @@ export function StudioApp() {
465
593
  compositionLoading,
466
594
  refreshKey,
467
595
  setRefreshKey,
468
- currentTime,
469
596
  timelineElements,
470
597
  isPlaying,
471
598
  editHistory,
@@ -513,6 +640,7 @@ export function StudioApp() {
513
640
  refreshCaptureFrameTime={frameCapture.refreshCaptureFrameTime}
514
641
  inspectorButtonActive={inspectorButtonActive}
515
642
  inspectorPanelActive={inspectorPanelActive}
643
+ onExport={() => void renderQueue.startRender()}
516
644
  />
517
645
 
518
646
  <div className="flex flex-1 min-h-0">
@@ -539,7 +667,23 @@ export function StudioApp() {
539
667
  setCompIdToSrc={setCompIdToSrc}
540
668
  setCompositionLoading={setCompositionLoading}
541
669
  shouldShowSelectedDomBounds={shouldShowSelectedDomBounds}
670
+ isGestureRecording={gestureState === "recording"}
542
671
  blockPreview={blockPreview}
672
+ gestureOverlay={
673
+ gestureState === "recording" && previewIframe ? (
674
+ <GestureTrailOverlay
675
+ samples={gestureRecording.samplesRef.current}
676
+ sampleCount={gestureRecording.samplesRef.current.length}
677
+ trail={gestureRecording.trailRef.current}
678
+ canvasRect={(() => {
679
+ const r = previewIframe.getBoundingClientRect();
680
+ return { left: r.left, top: r.top, width: r.width, height: r.height };
681
+ })()}
682
+ compositionSize={compositionDimensions ?? undefined}
683
+ mode="recording"
684
+ />
685
+ ) : undefined
686
+ }
543
687
  />
544
688
 
545
689
  {!panelLayout.rightCollapsed && (
@@ -552,6 +696,9 @@ export function StudioApp() {
552
696
  setActiveBlockParams(null);
553
697
  panelLayout.setRightPanelTab("design");
554
698
  }}
699
+ recordingState={gestureState}
700
+ recordingDuration={gestureRecording.recordingDuration}
701
+ onToggleRecording={handleToggleRecording}
555
702
  />
556
703
  )}
557
704
  </div>
@@ -584,7 +731,13 @@ export function StudioApp() {
584
731
  )}
585
732
 
586
733
  {dragOverlay.active && <StudioGlobalDragOverlay />}
587
- {appToast && <StudioToast message={appToast.message} tone={appToast.tone} />}
734
+ {appToast && (
735
+ <StudioToast
736
+ message={appToast.message}
737
+ tone={appToast.tone}
738
+ onDismiss={dismissToast}
739
+ />
740
+ )}
588
741
  </div>
589
742
  </DomEditProvider>
590
743
  </FileManagerProvider>
@@ -17,6 +17,7 @@ export interface StudioHeaderProps {
17
17
  refreshCaptureFrameTime: () => void;
18
18
  inspectorButtonActive: boolean;
19
19
  inspectorPanelActive: boolean;
20
+ onExport?: () => void;
20
21
  }
21
22
 
22
23
  function HyperframesLogo() {
@@ -147,6 +148,7 @@ export function StudioHeader({
147
148
  refreshCaptureFrameTime,
148
149
  inspectorButtonActive,
149
150
  inspectorPanelActive,
151
+ onExport,
150
152
  }: StudioHeaderProps) {
151
153
  const { projectId, editHistory, handleUndo, handleRedo } = useStudioContext();
152
154
  const { rightCollapsed, setRightCollapsed, setRightPanelTab } = usePanelLayoutContext();
@@ -171,10 +173,10 @@ export function StudioHeader({
171
173
  void handleUndo();
172
174
  }}
173
175
  disabled={!editHistory.canUndo}
174
- className={`h-7 w-7 flex items-center justify-center rounded-md border transition-colors ${
176
+ className={`h-7 w-7 flex items-center justify-center rounded-md transition-colors ${
175
177
  editHistory.canUndo
176
- ? "border-neutral-700 text-neutral-300 hover:border-neutral-500 hover:bg-neutral-800"
177
- : "border-neutral-900 text-neutral-700"
178
+ ? "text-neutral-400 hover:text-neutral-200 hover:bg-neutral-800"
179
+ : "text-neutral-700 cursor-default"
178
180
  }`}
179
181
  title={
180
182
  editHistory.undoLabel
@@ -192,10 +194,10 @@ export function StudioHeader({
192
194
  void handleRedo();
193
195
  }}
194
196
  disabled={!editHistory.canRedo}
195
- className={`h-7 w-7 flex items-center justify-center rounded-md border transition-colors ${
197
+ className={`h-7 w-7 flex items-center justify-center rounded-md transition-colors ${
196
198
  editHistory.canRedo
197
- ? "border-neutral-700 text-neutral-300 hover:border-neutral-500 hover:bg-neutral-800"
198
- : "border-neutral-900 text-neutral-700"
199
+ ? "text-neutral-400 hover:text-neutral-200 hover:bg-neutral-800"
200
+ : "text-neutral-700 cursor-default"
199
201
  }`}
200
202
  title={
201
203
  editHistory.redoLabel
@@ -215,7 +217,7 @@ export function StudioHeader({
215
217
  }}
216
218
  onFocus={refreshCaptureFrameTime}
217
219
  onPointerDown={refreshCaptureFrameTime}
218
- className="h-7 flex items-center gap-1.5 px-2.5 rounded-md text-[11px] font-medium border border-neutral-700 text-neutral-300 transition-colors hover:border-neutral-500 hover:bg-neutral-800"
220
+ className="h-7 flex items-center gap-1.5 px-2.5 rounded-md text-[11px] font-medium text-neutral-400 transition-colors hover:text-neutral-200 hover:bg-neutral-800"
219
221
  title="Capture current frame"
220
222
  aria-label="Capture current frame"
221
223
  >
@@ -264,6 +266,17 @@ export function StudioHeader({
264
266
  </svg>
265
267
  Inspector
266
268
  </button>
269
+ <button
270
+ type="button"
271
+ onClick={() => {
272
+ setRightPanelTab("renders");
273
+ setRightCollapsed(false);
274
+ onExport?.();
275
+ }}
276
+ className="h-7 flex items-center gap-1.5 px-3 rounded-md text-[11px] font-semibold bg-studio-accent text-[#09090B] hover:brightness-110 transition-colors"
277
+ >
278
+ Export
279
+ </button>
267
280
  </div>
268
281
  </div>
269
282
  );
@@ -56,6 +56,8 @@ export interface StudioPreviewAreaProps {
56
56
  setCompositionLoading: (loading: boolean) => void;
57
57
  shouldShowSelectedDomBounds: boolean;
58
58
  blockPreview?: BlockPreviewInfo | null;
59
+ isGestureRecording?: boolean;
60
+ gestureOverlay?: ReactNode;
59
61
  }
60
62
 
61
63
  // fallow-ignore-next-line complexity
@@ -74,7 +76,9 @@ export function StudioPreviewArea({
74
76
  setCompIdToSrc,
75
77
  setCompositionLoading,
76
78
  shouldShowSelectedDomBounds,
79
+ isGestureRecording,
77
80
  blockPreview,
81
+ gestureOverlay,
78
82
  }: StudioPreviewAreaProps) {
79
83
  const {
80
84
  projectId,
@@ -241,7 +245,7 @@ export function StudioPreviewArea({
241
245
  }
242
246
  selection={shouldShowSelectedDomBounds ? domEditSelection : null}
243
247
  groupSelections={shouldShowSelectedDomBounds ? domEditGroupSelections : []}
244
- allowCanvasMovement={STUDIO_PREVIEW_MANUAL_EDITING_ENABLED}
248
+ allowCanvasMovement={STUDIO_PREVIEW_MANUAL_EDITING_ENABLED && !isGestureRecording}
245
249
  onCanvasMouseDown={handlePreviewCanvasMouseDown}
246
250
  onCanvasPointerMove={handlePreviewCanvasPointerMove}
247
251
  onCanvasPointerLeave={handlePreviewCanvasPointerLeave}
@@ -256,6 +260,7 @@ export function StudioPreviewArea({
256
260
  gridSpacing={snapPrefs.gridSpacing}
257
261
  />
258
262
  <SnapToolbar onSnapChange={setSnapPrefs} />
263
+ {gestureOverlay}
259
264
  </>
260
265
  ) : null
261
266
  }
@@ -32,6 +32,9 @@ export interface StudioRightPanelProps {
32
32
  compositionPath: string;
33
33
  } | null;
34
34
  onCloseBlockParams?: () => void;
35
+ recordingState?: "idle" | "recording" | "preview";
36
+ recordingDuration?: number;
37
+ onToggleRecording?: () => void;
35
38
  }
36
39
 
37
40
  // fallow-ignore-next-line complexity
@@ -41,6 +44,9 @@ export function StudioRightPanel({
41
44
  motionPanelActive,
42
45
  activeBlockParams,
43
46
  onCloseBlockParams,
47
+ recordingState,
48
+ recordingDuration,
49
+ onToggleRecording,
44
50
  }: StudioRightPanelProps) {
45
51
  const {
46
52
  rightWidth,
@@ -92,6 +98,8 @@ export function StudioRightPanel({
92
98
  handleGsapAddFromProperty,
93
99
  handleGsapRemoveFromProperty,
94
100
  commitAnimatedProperty,
101
+ handleSetArcPath,
102
+ handleUpdateArcSegment,
95
103
  } = useDomEditContext();
96
104
 
97
105
  const { assets, fontAssets, projectDir, handleImportFiles, handleImportFonts } =
@@ -226,6 +234,11 @@ export function StudioRightPanel({
226
234
  onRemoveGsapFromProperty={handleGsapRemoveFromProperty}
227
235
  onAddGsapAnimation={handleGsapAddAnimation}
228
236
  onCommitAnimatedProperty={commitAnimatedProperty}
237
+ onSetArcPath={handleSetArcPath}
238
+ onUpdateArcSegment={handleUpdateArcSegment}
239
+ recordingState={recordingState}
240
+ recordingDuration={recordingDuration}
241
+ onToggleRecording={onToggleRecording}
229
242
  />
230
243
  ) : motionPanelActive ? (
231
244
  <MotionPanel
@@ -1,18 +1,58 @@
1
1
  interface StudioToastProps {
2
2
  message: string;
3
3
  tone?: "error" | "info";
4
+ onDismiss?: () => void;
4
5
  }
5
6
 
6
- export function StudioToast({ message, tone }: StudioToastProps) {
7
+ export function StudioToast({ message, tone, onDismiss }: StudioToastProps) {
8
+ const isError = tone === "error";
7
9
  return (
8
10
  <div
9
- className={`absolute bottom-6 left-1/2 -translate-x-1/2 z-[91] px-4 py-2 rounded-lg border text-sm shadow-lg animate-in fade-in slide-in-from-bottom-2 ${
10
- tone === "error"
11
- ? "bg-red-900/90 border-red-700/50 text-red-200"
12
- : "bg-neutral-900/95 border-neutral-700/60 text-neutral-100"
13
- }`}
11
+ className="absolute bottom-6 right-6 z-[91] animate-in fade-in slide-in-from-bottom-2"
12
+ onClick={onDismiss}
13
+ role={onDismiss ? "button" : undefined}
14
+ style={onDismiss ? { cursor: "pointer" } : undefined}
14
15
  >
15
- {message}
16
+ <div
17
+ className="relative flex items-center gap-3 overflow-hidden rounded-2xl pl-4 pr-2 py-3 text-[12px]"
18
+ style={{
19
+ background: isError
20
+ ? "linear-gradient(135deg, rgba(127,29,29,0.55), rgba(80,10,10,0.45))"
21
+ : "linear-gradient(135deg, rgba(38,38,38,0.55), rgba(23,23,23,0.45))",
22
+ backdropFilter: "blur(16px) saturate(1.6)",
23
+ WebkitBackdropFilter: "blur(16px) saturate(1.6)",
24
+ border: `1px solid ${isError ? "rgba(239,68,68,0.18)" : "rgba(255,255,255,0.08)"}`,
25
+ boxShadow: [
26
+ "0 8px 32px rgba(0,0,0,0.35)",
27
+ `inset 0 1px 0 ${isError ? "rgba(239,68,68,0.12)" : "rgba(255,255,255,0.06)"}`,
28
+ `inset 0 -1px 0 rgba(0,0,0,0.15)`,
29
+ ].join(", "),
30
+ }}
31
+ >
32
+ <span className={isError ? "text-red-200" : "text-neutral-200"}>{message}</span>
33
+ {onDismiss && (
34
+ <button
35
+ type="button"
36
+ onClick={(e) => {
37
+ e.stopPropagation();
38
+ onDismiss();
39
+ }}
40
+ className="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-md text-neutral-500 transition-colors hover:bg-white/10 hover:text-neutral-300"
41
+ aria-label="Dismiss"
42
+ >
43
+ <svg
44
+ width="10"
45
+ height="10"
46
+ viewBox="0 0 10 10"
47
+ fill="none"
48
+ stroke="currentColor"
49
+ strokeWidth="1.5"
50
+ >
51
+ <path d="M2 2l6 6M8 2l-6 6" />
52
+ </svg>
53
+ </button>
54
+ )}
55
+ </div>
16
56
  </div>
17
57
  );
18
58
  }