@hyperframes/studio 0.7.15 → 0.7.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/index-C4cUbqql.js +375 -0
- package/dist/assets/{index-CTXExGxO.js → index-D2SMcOdY.js} +1 -1
- package/dist/assets/index-DmkOvZns.css +1 -0
- package/dist/assets/{index-Db9KG66z.js → index-sCOjSz40.js} +1 -1
- package/dist/{chunk-UUEDZ4UJ.js → chunk-SBGXX7WY.js} +52 -22
- package/dist/chunk-SBGXX7WY.js.map +1 -0
- package/dist/{domEditingLayers-NWCBED4R.js → domEditingLayers-VZMLL4AP.js} +2 -4
- package/dist/index.d.ts +18 -1
- package/dist/index.html +2 -2
- package/dist/index.js +3157 -2808
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/App.tsx +2 -0
- package/src/components/StudioPreviewArea.tsx +0 -41
- package/src/components/StudioRightPanel.tsx +2 -0
- package/src/components/TimelineToolbar.tsx +4 -4
- package/src/components/editor/DomEditOverlay.tsx +5 -2
- package/src/components/editor/GestureRecordControl.tsx +2 -2
- package/src/components/editor/GsapAnimationSection.tsx +3 -3
- package/src/components/editor/InspectorHeaderActions.tsx +62 -0
- package/src/components/editor/LayersPanel.tsx +35 -2
- package/src/components/editor/MotionPathOverlay.tsx +24 -8
- package/src/components/editor/PropertyPanel.tsx +48 -60
- package/src/components/editor/Transform3DCube.tsx +84 -85
- package/src/components/editor/domEditOverlayGeometry.ts +28 -0
- package/src/components/editor/domEditingDom.ts +36 -2
- package/src/components/editor/domEditingGroups.ts +41 -0
- package/src/components/editor/domEditingLayers.test.ts +97 -1
- package/src/components/editor/domEditingLayers.ts +13 -29
- package/src/components/editor/domEditingTypes.ts +3 -0
- package/src/components/editor/manualOffsetDrag.ts +23 -2
- package/src/components/editor/propertyPanel3dTransform.tsx +70 -30
- package/src/components/editor/propertyPanelHelpers.ts +2 -0
- package/src/components/editor/useDomEditOverlayRects.ts +8 -3
- package/src/components/editor/useMotionPathData.ts +47 -1
- package/src/contexts/DomEditContext.tsx +16 -0
- package/src/contexts/TimelineEditContext.tsx +0 -1
- package/src/hooks/gsapRuntimeBridge.ts +9 -2
- package/src/hooks/gsapTweenSynth.ts +64 -0
- package/src/hooks/useAnimatedPropertyCommit.ts +126 -26
- package/src/hooks/useAppHotkeys.ts +34 -3
- package/src/hooks/useDomEditSession.ts +48 -0
- package/src/hooks/useDomSelection.ts +73 -9
- package/src/hooks/useEnableKeyframes.test.ts +35 -0
- package/src/hooks/useEnableKeyframes.ts +29 -1
- package/src/hooks/useGroupCommits.ts +188 -0
- package/src/hooks/useGsapSelectionHandlers.ts +4 -0
- package/src/hooks/useGsapTweenCache.ts +56 -72
- package/src/hooks/usePreviewInteraction.ts +60 -2
- package/src/player/components/ShortcutsPanel.tsx +2 -0
- package/src/player/components/Timeline.tsx +0 -15
- package/src/player/components/TimelineCanvas.tsx +0 -13
- package/src/player/components/TimelineClipDiamonds.tsx +4 -125
- package/src/player/components/timelineCallbacks.ts +0 -1
- package/src/player/hooks/useExpandedTimelineElements.test.ts +43 -0
- package/src/player/hooks/useExpandedTimelineElements.ts +47 -4
- package/src/player/hooks/useTimelineSyncCallbacks.ts +43 -4
- package/src/player/store/playerStore.ts +20 -0
- package/src/utils/studioPreviewHelpers.ts +37 -0
- package/dist/assets/index-B7VWFKy1.js +0 -375
- package/dist/assets/index-svYFaNuq.css +0 -1
- package/dist/chunk-UUEDZ4UJ.js.map +0 -1
- package/src/components/editor/keyframeMove.test.ts +0 -101
- package/src/components/editor/keyframeMove.ts +0 -151
- /package/dist/{domEditingLayers-NWCBED4R.js.map → domEditingLayers-VZMLL4AP.js.map} +0 -0
|
@@ -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}.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-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\.5{left:.375rem}.left-1\/2{left:50%}.left-2{left:.5rem}.left-3{left:.75rem}.right-0{right:0}.right-1{right:.25rem}.right-1\.5{right:.375rem}.right-2{right:.5rem}.right-3{right:.75rem}.right-6{right:1.5rem}.top-0{top:0}.top-1{top:.25rem}.top-1\.5{top:.375rem}.top-1\/2{top:50%}.top-14{top:3.5rem}.top-2{top:.5rem}.top-3{top:.75rem}.top-\[2px\]{top:2px}.top-\[calc\(100\%\+6px\)\]{top:calc(100% + 6px)}.top-full{top:100%}.isolate{isolation:isolate}.z-0{z-index:0}.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-\[92\]{z-index:92}.z-\[9999\]{z-index:9999}.col-span-2{grid-column:span 2 / span 2}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-auto{margin-left:auto;margin-right:auto}.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-2\.5{margin-bottom:.625rem}.mb-3{margin-bottom:.75rem}.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-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-8{margin-top:2rem}.mt-\[22px\]{margin-top:22px}.mt-\[5px\]{margin-top:5px}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.\!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}.size-2\.5{width:.625rem;height:.625rem}.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-\[100px\]{height:100px}.h-\[14px\]{height:14px}.h-\[32px\]{height:32px}.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-40{max-height:10rem}.max-h-64{max-height:16rem}.max-h-\[420px\]{max-height:420px}.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-\[120px\]{min-height:120px}.min-h-\[240px\]{min-height:240px}.w-1\.5{width:.375rem}.w-1\/2{width:50%}.w-10{width:2.5rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\/5{width:40%}.w-20{width:5rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-3\/5{width:60%}.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-\[118px\]{width:118px}.w-\[160px\]{width:160px}.w-\[292px\]{width:292px}.w-\[38px\]{width:38px}.w-\[480px\]{width:480px}.w-\[50px\]{width:50px}.w-\[560px\]{width:560px}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-6{min-width:1.5rem}.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-\[16px\]{min-width:16px}.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-\[1400px\]{max-width:1400px}.max-w-\[184px\]{max-width:184px}.max-w-\[260px\]{max-width:260px}.max-w-\[280px\]{max-width:280px}.max-w-\[900px\]{max-width:900px}.max-w-\[calc\(100vw-32px\)\]{max-width:calc(100vw - 32px)}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.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}.select-all{-webkit-user-select:all;-moz-user-select:all;user-select:all}.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-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-\[minmax\(0\,1fr\)_28px\]{grid-template-columns:minmax(0,1fr) 28px}.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}.items-baseline{align-items:baseline}.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-px{gap:1px}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-5{-moz-column-gap:1.25rem;column-gap:1.25rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.gap-y-1{row-gap:.25rem}.gap-y-1\.5{row-gap:.375rem}.gap-y-2{row-gap:.5rem}.gap-y-8{row-gap:2rem}.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))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * 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-\[14px\]{border-radius:14px}.rounded-\[18px\]{border-radius:18px}.rounded-\[1px\]{border-radius:1px}.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-y{border-top-width:1px;border-bottom-width:1px}.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-neutral-900{--tw-border-opacity: 1;border-color:rgb(23 23 23 / var(--tw-border-opacity, 1))}.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-300\/20{border-color:#fca5a533}.border-red-400\/60{border-color:#f8717199}.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\/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\/20{border-color:#fff3}.border-white\/5{border-color:#ffffff0d}.border-white\/90{border-color:#ffffffe6}.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-\[\#3CE6AC\]\/10{background-color:#3ce6ac1a}.bg-\[\#3CE6AC\]\/5{background-color:#3ce6ac0d}.bg-\[\#5ff0bf\]{--tw-bg-opacity: 1;background-color:rgb(95 240 191 / var(--tw-bg-opacity, 1))}.bg-amber-300{--tw-bg-opacity: 1;background-color:rgb(252 211 77 / var(--tw-bg-opacity, 1))}.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-amber-500\/20{background-color:#f59e0b33}.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\/40{background-color:#0006}.bg-black\/50{background-color:#00000080}.bg-black\/60{background-color:#0009}.bg-black\/70{background-color:#000000b3}.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-400{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-emerald-500\/15{background-color:#10b98126}.bg-emerald-500\/20{background-color:#10b98133}.bg-emerald-600{--tw-bg-opacity: 1;background-color:rgb(5 150 105 / 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-200{--tw-bg-opacity: 1;background-color:rgb(229 229 229 / var(--tw-bg-opacity, 1))}.bg-neutral-500{--tw-bg-opacity: 1;background-color:rgb(115 115 115 / 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\/40{background-color:#17171766}.bg-neutral-900\/50{background-color:#17171780}.bg-neutral-900\/60{background-color:#17171799}.bg-neutral-900\/80{background-color:#171717cc}.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\/15{background-color:#3ce6ac26}.bg-panel-accent\/40{background-color:#3ce6ac66}.bg-panel-accent\/\[0\.06\]{background-color:#3ce6ac0f}.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-panel-input\/30{background-color:#1616184d}.bg-panel-text-3{--tw-bg-opacity: 1;background-color:rgb(113 113 122 / var(--tw-bg-opacity, 1))}.bg-panel-text-5{--tw-bg-opacity: 1;background-color:rgb(63 63 70 / 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{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / 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-red-950\/85{background-color:#450a0ad9}.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-sky-400{--tw-bg-opacity: 1;background-color:rgb(56 189 248 / var(--tw-bg-opacity, 1))}.bg-sky-500\/20{background-color:#0ea5e933}.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\/80{background-color:#3ce6accc}.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\/5{background-color:#ffffff0d}.bg-white\/70{background-color:#ffffffb3}.bg-gradient-to-b{background-image:linear-gradient(to bottom,var(--tw-gradient-stops))}.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-neutral-900{--tw-gradient-from: #171717 var(--tw-gradient-from-position);--tw-gradient-to: rgb(23 23 23 / 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-neutral-950{--tw-gradient-to: #0a0a0a var(--tw-gradient-to-position)}.to-transparent{--tw-gradient-to: transparent var(--tw-gradient-to-position)}.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-3{padding:.75rem}.p-4{padding:1rem}.p-8{padding:2rem}.p-\[2px\]{padding:2px}.px-0\.5{padding-left:.125rem;padding-right:.125rem}.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-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.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-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-1\.5{padding-bottom:.375rem}.pb-2{padding-bottom:.5rem}.pb-2\.5{padding-bottom:.625rem}.pb-3{padding-bottom:.75rem}.pb-5{padding-bottom:1.25rem}.pl-1{padding-left:.25rem}.pl-2{padding-left:.5rem}.pl-4{padding-left:1rem}.pl-7{padding-left:1.75rem}.pr-2{padding-right:.5rem}.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}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.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-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.normal-case{text-transform:none}.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-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-\[0\.14em\]{letter-spacing:.14em}.tracking-\[0\.16em\]{letter-spacing:.16em}.tracking-tight{letter-spacing:-.025em}.tracking-wide{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-\[\#5ff0bf\]{--tw-text-opacity: 1;color:rgb(95 240 191 / 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-emerald-300{--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / 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-900{--tw-text-opacity: 1;color:rgb(23 23 23 / 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-panel-text-5\/30{color:#3f3f464d}.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-100{--tw-text-opacity: 1;color:rgb(254 226 226 / 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-sky-300{--tw-text-opacity: 1;color:rgb(125 211 252 / 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}.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}.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-\[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-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\/30{--tw-shadow-color: rgb(0 0 0 / .3);--tw-shadow: var(--tw-shadow-colored)}.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-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)}.sepia{--tw-sepia: sepia(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-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-\[r\]{transition-property:r;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)}.\[grid-template-columns\:repeat\(auto-fill\,minmax\(300px\,1fr\)\)\]{grid-template-columns:repeat(auto-fill,minmax(300px,1fr))}: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}.hf-color-grading-number::-webkit-outer-spin-button,.hf-color-grading-number::-webkit-inner-spin-button{margin:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.hf-color-grading-number{-moz-appearance:textfield;-webkit-appearance:textfield;appearance:textfield}.hf-color-grading-range{height:1.5rem;cursor:default;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent}.hf-color-grading-range:disabled{cursor:not-allowed;opacity:.5}.hf-color-grading-range::-webkit-slider-runnable-track{height:1.25rem;border:0;background:transparent}.hf-color-grading-range::-webkit-slider-thumb{width:.625rem;height:1rem;margin-top:.125rem;border:0;border-radius:999px;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;box-shadow:0 0 0 2px #0c0c0e,0 1px 4px #0000008c;cursor:default}.hf-color-grading-range::-moz-range-track{height:1.25rem;border:0;background:transparent}.hf-color-grading-range::-moz-range-thumb{width:.625rem;height:1rem;border:0;border-radius:999px;background:#fff;box-shadow:0 0 0 2px #0c0c0e,0 1px 4px #0000008c;cursor:default}.hf-color-grading-range:focus-visible{outline:none}.hf-color-grading-range:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 2px #0c0c0e,0 0 0 4px #3ce6ac38,0 1px 4px #0000008c}.hf-color-grading-range:focus-visible::-moz-range-thumb{box-shadow:0 0 0 2px #0c0c0e,0 0 0 4px #3ce6ac38,0 1px 4px #0000008c}#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))}.placeholder\:text-panel-text-5::-moz-placeholder{--tw-text-opacity: 1;color:rgb(63 63 70 / var(--tw-text-opacity, 1))}.placeholder\:text-panel-text-5::placeholder{--tw-text-opacity: 1;color:rgb(63 63 70 / var(--tw-text-opacity, 1))}.last\:border-0:last-child{border-width:0px}.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:hover{--tw-border-opacity: 1;border-color:rgb(60 230 172 / 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:hover{--tw-bg-opacity: 1;background-color:rgb(64 64 64 / 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\/60:hover{background-color:#26262699}.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-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-400\/10:hover{background-color:#f871711a}.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\/10:hover{background-color:#3ce6ac1a}.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\:text-\[\#22c55e\]:hover{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.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\: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-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\: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\: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:hover .group-hover\:text-panel-text-3{--tw-text-opacity: 1;color:rgb(113 113 122 / var(--tw-text-opacity, 1))}.group\/card:hover .group-hover\/card\:opacity-100{opacity:1}.group:active .group-active\:bg-studio-accent\/70{background-color:#3ce6acb3}@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-\[10px\]::-webkit-slider-thumb{height:10px}.\[\&\:\:-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\]\: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)}.\[\&_a\]\:text-sky-400 a{--tw-text-opacity: 1;color:rgb(56 189 248 / var(--tw-text-opacity, 1))}.\[\&_code\]\:rounded code{border-radius:.25rem}.\[\&_code\]\:bg-neutral-800 code{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.\[\&_code\]\:px-1 code{padding-left:.25rem;padding-right:.25rem}.\[\&_code\]\:text-\[0\.85em\] code{font-size:.85em}.\[\&_code\]\:text-neutral-200 code{--tw-text-opacity: 1;color:rgb(229 229 229 / var(--tw-text-opacity, 1))}.\[\&_h1\]\:mb-2 h1{margin-bottom:.5rem}.\[\&_h1\]\:mt-5 h1{margin-top:1.25rem}.\[\&_h1\]\:text-xl h1{font-size:1.25rem;line-height:1.75rem}.\[\&_h1\]\:font-semibold h1{font-weight:600}.\[\&_h1\]\:text-neutral-100 h1{--tw-text-opacity: 1;color:rgb(245 245 245 / var(--tw-text-opacity, 1))}.\[\&_h2\]\:mb-1\.5 h2{margin-bottom:.375rem}.\[\&_h2\]\:mt-5 h2{margin-top:1.25rem}.\[\&_h2\]\:text-base h2{font-size:1rem;line-height:1.5rem}.\[\&_h2\]\:font-semibold h2{font-weight:600}.\[\&_h2\]\:text-neutral-100 h2{--tw-text-opacity: 1;color:rgb(245 245 245 / var(--tw-text-opacity, 1))}.\[\&_h3\]\:mt-4 h3{margin-top:1rem}.\[\&_h3\]\:font-semibold h3{font-weight:600}.\[\&_h3\]\:text-neutral-200 h3{--tw-text-opacity: 1;color:rgb(229 229 229 / var(--tw-text-opacity, 1))}.\[\&_hr\]\:my-4 hr{margin-top:1rem;margin-bottom:1rem}.\[\&_hr\]\:border-neutral-800 hr{--tw-border-opacity: 1;border-color:rgb(38 38 38 / var(--tw-border-opacity, 1))}.\[\&_img\]\:my-2 img{margin-top:.5rem;margin-bottom:.5rem}.\[\&_img\]\:h-auto img{height:auto}.\[\&_img\]\:max-w-full img{max-width:100%}.\[\&_img\]\:rounded img{border-radius:.25rem}.\[\&_ol\]\:my-2 ol{margin-top:.5rem;margin-bottom:.5rem}.\[\&_ol\]\:list-decimal ol{list-style-type:decimal}.\[\&_ol\]\:pl-5 ol{padding-left:1.25rem}.\[\&_p\]\:my-2 p{margin-top:.5rem;margin-bottom:.5rem}.\[\&_pre\]\:my-3 pre{margin-top:.75rem;margin-bottom:.75rem}.\[\&_pre\]\:overflow-auto pre{overflow:auto}.\[\&_pre\]\:rounded pre{border-radius:.25rem}.\[\&_pre\]\:bg-neutral-900 pre{--tw-bg-opacity: 1;background-color:rgb(23 23 23 / var(--tw-bg-opacity, 1))}.\[\&_pre\]\:p-3 pre{padding:.75rem}.\[\&_pre_code\]\:bg-transparent pre code{background-color:transparent}.\[\&_pre_code\]\:p-0 pre code{padding:0}.\[\&_strong\]\:text-neutral-100 strong{--tw-text-opacity: 1;color:rgb(245 245 245 / var(--tw-text-opacity, 1))}.\[\&_table\]\:my-3 table{margin-top:.75rem;margin-bottom:.75rem}.\[\&_td\]\:border td{border-width:1px}.\[\&_td\]\:border-neutral-800 td{--tw-border-opacity: 1;border-color:rgb(38 38 38 / var(--tw-border-opacity, 1))}.\[\&_td\]\:px-2 td{padding-left:.5rem;padding-right:.5rem}.\[\&_td\]\:py-1 td{padding-top:.25rem;padding-bottom:.25rem}.\[\&_th\]\:border th{border-width:1px}.\[\&_th\]\:border-neutral-800 th{--tw-border-opacity: 1;border-color:rgb(38 38 38 / var(--tw-border-opacity, 1))}.\[\&_th\]\:px-2 th{padding-left:.5rem;padding-right:.5rem}.\[\&_th\]\:py-1 th{padding-top:.25rem;padding-bottom:.25rem}.\[\&_ul\]\:my-2 ul{margin-top:.5rem;margin-bottom:.5rem}.\[\&_ul\]\:list-disc ul{list-style-type:disc}.\[\&_ul\]\:pl-5 ul{padding-left:1.25rem}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/editor/domEditingTypes.ts","../src/components/editor/domEditingDom.ts","../src/components/editor/domEditingElement.ts","../src/components/editor/domEditingRootLayer.ts","../src/components/editor/domEditingLayers.ts"],"sourcesContent":["import type { PatchTarget } from \"../../utils/sourcePatcher\";\nimport type { GsapAnimation } from \"@hyperframes/parsers/gsap-parser\";\n\nexport const CURATED_STYLE_PROPERTIES = [\n \"position\",\n \"display\",\n \"top\",\n \"left\",\n \"right\",\n \"bottom\",\n \"inset\",\n \"width\",\n \"height\",\n \"gap\",\n \"justify-content\",\n \"align-items\",\n \"flex-direction\",\n \"font-size\",\n \"font-style\",\n \"font-weight\",\n \"font-family\",\n \"line-height\",\n \"letter-spacing\",\n \"text-align\",\n \"text-transform\",\n \"color\",\n \"background-color\",\n \"background-image\",\n \"opacity\",\n \"mix-blend-mode\",\n \"border-radius\",\n \"border-width\",\n \"border-style\",\n \"border-color\",\n \"border-top-width\",\n \"border-top-style\",\n \"border-top-color\",\n \"outline-color\",\n \"overflow\",\n \"clip-path\",\n \"box-shadow\",\n \"filter\",\n \"backdrop-filter\",\n \"z-index\",\n \"transform\",\n \"object-fit\",\n \"object-position\",\n] as const;\n\nexport interface DomEditCapabilities {\n canSelect: boolean;\n canEditStyles: boolean;\n /** Directly editable authored left/top style fields. Canvas drag uses manual edits instead. */\n canMove: boolean;\n /** Directly editable authored width/height style fields. Canvas resize uses manual edits instead. */\n canResize: boolean;\n canApplyManualOffset: boolean;\n canApplyManualSize: boolean;\n canApplyManualRotation: boolean;\n reasonIfDisabled?: string;\n}\n\nexport interface DomEditTextField {\n key: string;\n label: string;\n value: string;\n tagName: string;\n attributes: Array<{ name: string; value: string }>;\n inlineStyles: Record<string, string>;\n computedStyles: Record<string, string>;\n source: \"self\" | \"child\" | \"text-node\";\n}\n\nexport interface DomEditSelection extends PatchTarget {\n element: HTMLElement;\n label: string;\n tagName: string;\n sourceFile: string;\n compositionPath: string;\n compositionSrc?: string;\n isCompositionHost: boolean;\n isInsideLockedComposition: boolean;\n boundingBox: { x: number; y: number; width: number; height: number };\n textContent: string | null;\n dataAttributes: Record<string, string>;\n inlineStyles: Record<string, string>;\n computedStyles: Record<string, string>;\n textFields: DomEditTextField[];\n capabilities: DomEditCapabilities;\n gsapAnimations?: GsapAnimation[];\n}\n\nexport interface DomEditLayerItem {\n key: string;\n element: HTMLElement;\n label: string;\n tagName: string;\n depth: number;\n childCount: number;\n id?: string;\n hfId?: string;\n selector?: string;\n selectorIndex?: number;\n sourceFile: string;\n}\n\nexport interface DomEditContextOptions {\n activeCompositionPath: string | null;\n isMasterView: boolean;\n preferClipAncestor?: boolean;\n}\n\nexport interface DomEditViewport {\n width: number;\n height: number;\n}\n\nexport interface TimelineElementDomTarget {\n id?: string;\n domId?: string;\n selector?: string;\n selectorIndex?: number;\n sourceFile?: string;\n compositionSrc?: string;\n}\n\nexport interface TimelineElementDomTargetOptions {\n activeCompositionPath: string | null;\n compIdToSrc?: ReadonlyMap<string, string>;\n isMasterView: boolean;\n}\n","/**\n * Low-level DOM primitives: type guards, style getters, CSS escaping,\n * selector utilities, and composition source resolution.\n * No imports from other domEditing* modules — safe to import from anywhere.\n */\nimport { CURATED_STYLE_PROPERTIES } from \"./domEditingTypes\";\n\n// ─── Type guard ───────────────────────────────────────────────────────────────\n\nexport function isHtmlElement(value: unknown): value is HTMLElement {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"nodeType\" in value &&\n typeof (value as { nodeType?: unknown }).nodeType === \"number\" &&\n (value as { nodeType: number }).nodeType === 1\n );\n}\n\n// ─── Style parsing ────────────────────────────────────────────────────────────\n\nexport function parsePx(value: string | undefined): number | null {\n if (!value) return null;\n const trimmed = value.trim();\n if (!trimmed.endsWith(\"px\")) return null;\n const parsed = parseFloat(trimmed);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nexport function isIdentityTransform(value: string | undefined): boolean {\n const transform = (value ?? \"none\").trim();\n if (!transform || transform === \"none\") return true;\n\n const matrix = transform.match(/^matrix\\(([^)]+)\\)$/i);\n if (matrix) {\n const values = matrix[1].split(\",\").map((part) => Number.parseFloat(part.trim()));\n if (values.length !== 6 || values.some((part) => !Number.isFinite(part))) return false;\n return (\n Math.abs(values[0] - 1) < 0.0001 &&\n Math.abs(values[1]) < 0.0001 &&\n Math.abs(values[2]) < 0.0001 &&\n Math.abs(values[3] - 1) < 0.0001 &&\n Math.abs(values[4]) < 0.0001 &&\n Math.abs(values[5]) < 0.0001\n );\n }\n\n const matrix3d = transform.match(/^matrix3d\\(([^)]+)\\)$/i);\n if (!matrix3d) return false;\n const values = matrix3d[1].split(\",\").map((part) => Number.parseFloat(part.trim()));\n if (values.length !== 16 || values.some((part) => !Number.isFinite(part))) return false;\n const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];\n return values.every((part, index) => Math.abs(part - identity[index]) < 0.0001);\n}\n\nexport function isTextBearingTag(tagName: string): boolean {\n return [\"div\", \"span\", \"p\", \"strong\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"].includes(tagName);\n}\n\nconst COLOR_GRADING_SOURCE_HIDDEN_ATTR = \"data-hf-color-grading-source-hidden\";\n\nexport function isElementVisibleThroughAncestors(el: HTMLElement): boolean {\n const win = el.ownerDocument.defaultView;\n if (!win) return true;\n let current: HTMLElement | null = el;\n while (current) {\n const computed = win.getComputedStyle(current);\n if (computed.display === \"none\" || computed.visibility === \"hidden\") return false;\n const opacity = Number.parseFloat(computed.opacity);\n if (\n Number.isFinite(opacity) &&\n opacity <= 0.01 &&\n !current.hasAttribute(COLOR_GRADING_SOURCE_HIDDEN_ATTR)\n )\n return false;\n current = current.parentElement;\n }\n return true;\n}\n\n// ─── Style accessors ──────────────────────────────────────────────────────────\n\nexport function getCuratedComputedStyles(el: HTMLElement): Record<string, string> {\n const styles: Record<string, string> = {};\n const computed = el.ownerDocument.defaultView?.getComputedStyle(el);\n if (!computed) return styles;\n\n for (const prop of CURATED_STYLE_PROPERTIES) {\n const value = computed.getPropertyValue(prop);\n if (value) styles[prop] = value;\n }\n\n return styles;\n}\n\nexport function getInlineStyles(el: HTMLElement): Record<string, string> {\n const styles: Record<string, string> = {};\n for (const property of CURATED_STYLE_PROPERTIES) {\n const value = el.style.getPropertyValue(property);\n if (value) styles[property] = value;\n }\n return styles;\n}\n\nexport function getDataAttributes(el: HTMLElement): Record<string, string> {\n const attrs: Record<string, string> = {};\n for (const attr of el.attributes) {\n if (attr.name.startsWith(\"data-\")) {\n attrs[attr.name.slice(5)] = attr.value;\n }\n }\n return attrs;\n}\n\n// ─── DOM traversal ────────────────────────────────────────────────────────────\n\nexport function findClosestByAttribute(\n el: HTMLElement,\n attributeNames: string[],\n): HTMLElement | null {\n let current: HTMLElement | null = el;\n while (current) {\n const candidate = current;\n if (attributeNames.some((attribute) => candidate.hasAttribute(attribute))) {\n return candidate;\n }\n current = current.parentElement;\n }\n return null;\n}\n// ─── Composition source resolution ───────────────────────────────────────────\n\n// The runtime INLINES subcompositions and strips the source-file linkage from the\n// mounted root (it keeps `data-composition-id` but drops `data-composition-src`/\n// `-file`), so a subcomp element's DOM ancestors no longer say which file it came\n// from. This project-global map (composition-id → source file, built once from\n// index.html's clips — see NLELayout) recovers it. The studio loads one project at a\n// time, so module scope is the right lifetime; it's empty until set, in which case\n// resolution falls back to the historical attribute-only behavior.\nlet compositionSourceMap: Map<string, string> = new Map();\n\nexport function setCompositionSourceMap(map: Map<string, string>): void {\n compositionSourceMap = map;\n}\n\nfunction sourceFromCompositionId(ownerRoot: HTMLElement | null): string | undefined {\n if (!ownerRoot || compositionSourceMap.size === 0) return undefined;\n // The runtime may rename the mounted id to a runtime-unique one, preserving the\n // authored id on `data-hf-original-composition-id` — prefer that, then the current id.\n const authored = ownerRoot.getAttribute(\"data-hf-original-composition-id\");\n const current = ownerRoot.getAttribute(\"data-composition-id\");\n return (\n (authored ? compositionSourceMap.get(authored) : undefined) ??\n (current ? compositionSourceMap.get(current) : undefined)\n );\n}\n\nexport function getSourceFileForElement(\n el: HTMLElement,\n activeCompositionPath: string | null,\n): { sourceFile: string; compositionPath: string } {\n const sourceHost = findClosestByAttribute(el, [\"data-composition-file\", \"data-composition-src\"]);\n const ownerRoot = findClosestByAttribute(el, [\"data-composition-id\"]);\n const sourceFile =\n sourceHost?.getAttribute(\"data-composition-file\") ??\n sourceHost?.getAttribute(\"data-composition-src\") ??\n ownerRoot?.getAttribute(\"data-composition-file\") ??\n ownerRoot?.getAttribute(\"data-composition-src\") ??\n sourceFromCompositionId(ownerRoot) ??\n activeCompositionPath ??\n \"index.html\";\n\n return {\n sourceFile,\n compositionPath: sourceFile,\n };\n}\n\nexport function normalizeTimelineCompositionSource(value: string | undefined): string | undefined {\n const trimmed = value?.trim();\n if (!trimmed) return undefined;\n\n let pathname = trimmed;\n try {\n pathname = new URL(trimmed, \"http://studio.local\").pathname;\n } catch {\n pathname = trimmed;\n }\n\n for (const marker of [\"/preview/comp/\", \"/preview/\"]) {\n const markerIndex = pathname.indexOf(marker);\n if (markerIndex < 0) continue;\n const sourcePath = pathname.slice(markerIndex + marker.length).replace(/^\\/+/, \"\");\n return sourcePath || trimmed;\n }\n\n return trimmed;\n}\n\n// ─── CSS escaping ─────────────────────────────────────────────────────────────\n\nfunction escapeCssIdentifier(value: string): string {\n const css = globalThis.CSS as { escape?: (input: string) => string } | undefined;\n if (typeof css?.escape === \"function\") return css.escape(value);\n\n if (value === \"-\") return \"\\\\-\";\n\n let escaped = \"\";\n for (let index = 0; index < value.length; index += 1) {\n const char = value[index] ?? \"\";\n const code = char.charCodeAt(0);\n if (code === 0) {\n escaped += \"�\";\n continue;\n }\n\n const isDigit = code >= 48 && code <= 57;\n const isUpperAlpha = code >= 65 && code <= 90;\n const isLowerAlpha = code >= 97 && code <= 122;\n const isControl = (code >= 1 && code <= 31) || code === 127;\n const isLeadingDigit = index === 0 && isDigit;\n const isSecondDigitAfterDash = index === 1 && value.startsWith(\"-\") && isDigit;\n if (isControl || isLeadingDigit || isSecondDigitAfterDash) {\n escaped += `\\\\${code.toString(16)} `;\n continue;\n }\n if (isUpperAlpha || isLowerAlpha || isDigit || char === \"-\" || char === \"_\" || code >= 128) {\n escaped += char;\n continue;\n }\n escaped += `\\\\${char}`;\n }\n return escaped;\n}\n\nexport function escapeCssString(value: string): string {\n return value\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, \"\\\\a \")\n .replace(/\\r/g, \"\\\\d \")\n .replace(/\\f/g, \"\\\\c \");\n}\n\nexport function querySelectorAllSafely(doc: Document, selector: string): Element[] {\n try {\n return Array.from(doc.querySelectorAll(selector));\n } catch {\n return [];\n }\n}\n\nexport function humanizeIdentifier(value: string): string {\n return (\n value\n .replace(/\\.html$/i, \"\")\n .replace(/^compositions\\//i, \"\")\n .split(\"/\")\n .at(-1)\n ?.replace(/[-_]+/g, \" \")\n .replace(/\\b\\w/g, (char) => char.toUpperCase()) ?? value\n );\n}\n\n// ─── CSS selector building ────────────────────────────────────────────────────\n\nexport function buildStableSelector(el: HTMLElement): string | undefined {\n if (el.id) return `#${escapeCssIdentifier(el.id)}`;\n\n const compositionId = el.getAttribute(\"data-composition-id\");\n if (compositionId) return `[data-composition-id=\"${escapeCssString(compositionId)}\"]`;\n\n return getPreferredClassSelector(el);\n}\n\nexport function getPreferredClassSelector(el: HTMLElement): string | undefined {\n const classes = Array.from(el.classList)\n .map((value) => value.trim())\n .filter(Boolean);\n if (classes.length === 0) return undefined;\n const preferred =\n classes.find((value) => value !== \"clip\" && !value.startsWith(\"__hf-\")) ?? classes[0];\n return preferred ? `.${escapeCssIdentifier(preferred)}` : undefined;\n}\n\nexport function getSelectorIndex(\n doc: Document,\n el: HTMLElement,\n selector: string | undefined,\n sourceFile: string,\n activeCompositionPath: string | null,\n): number | undefined {\n if (!selector?.startsWith(\".\")) return undefined;\n\n const candidates = querySelectorAllSafely(doc, selector).filter(\n (candidate): candidate is HTMLElement =>\n isHtmlElement(candidate) &&\n getSourceFileForElement(candidate, activeCompositionPath).sourceFile === sourceFile,\n );\n const index = candidates.indexOf(el);\n return index >= 0 ? index : undefined;\n}\n","/**\n * Element visibility, visual scoring, layer patch targets, element finders,\n * and the `findElementForSelection` / `findElementForTimelineElement` lookups.\n */\nimport type {\n DomEditContextOptions,\n DomEditSelection,\n DomEditViewport,\n TimelineElementDomTarget,\n TimelineElementDomTargetOptions,\n} from \"./domEditingTypes\";\nimport {\n buildStableSelector,\n escapeCssString,\n getSelectorIndex,\n getSourceFileForElement,\n isHtmlElement,\n isElementVisibleThroughAncestors,\n normalizeTimelineCompositionSource,\n querySelectorAllSafely,\n} from \"./domEditingDom\";\n\n// ─── Visibility ──────────────────────────────────────────────────────────────\n\nexport function isElementComputedVisible(el: HTMLElement): boolean {\n return isElementVisibleThroughAncestors(el);\n}\n\nconst VISUAL_LEAF_TAGS = new Set([\"img\", \"video\", \"canvas\", \"svg\", \"audio\"]);\n\nfunction hasVisualPresence(el: HTMLElement): boolean {\n const win = el.ownerDocument.defaultView;\n if (!win) return false;\n const cs = win.getComputedStyle(el);\n if (cs.backgroundImage !== \"none\") return true;\n if (\n cs.backgroundColor &&\n cs.backgroundColor !== \"transparent\" &&\n cs.backgroundColor !== \"rgba(0, 0, 0, 0)\"\n )\n return true;\n if (cs.borderWidth && parseFloat(cs.borderWidth) > 0 && cs.borderStyle !== \"none\") return true;\n if (cs.boxShadow && cs.boxShadow !== \"none\") return true;\n return false;\n}\n\nfunction isEmptyVisualContainer(el: HTMLElement): boolean {\n const tag = el.tagName.toLowerCase();\n if (VISUAL_LEAF_TAGS.has(tag)) return false;\n if (hasVisualPresence(el)) return false;\n\n const { children } = el;\n if (children.length === 0) {\n return (el.textContent ?? \"\").trim().length === 0;\n }\n\n for (let i = 0; i < children.length; i += 1) {\n const child = children[i];\n if (!isHtmlElement(child)) continue;\n if (VISUAL_LEAF_TAGS.has(child.tagName.toLowerCase())) return false;\n if (isElementComputedVisible(child)) return false;\n }\n\n return true;\n}\n\nfunction hasRenderedBox(el: HTMLElement): boolean {\n const rect = el.getBoundingClientRect();\n if (rect.width <= 1 || rect.height <= 1) return false;\n if (!isElementComputedVisible(el)) return false;\n if (isEmptyVisualContainer(el)) return false;\n return true;\n}\n\n// ─── Visual scoring ──────────────────────────────────────────────────────────\n\n// ─── Layer patch target ──────────────────────────────────────────────────────\n\nconst DOM_LAYER_IGNORED_TAGS = new Set([\n \"base\",\n \"br\",\n \"canvas\",\n \"link\",\n \"meta\",\n \"script\",\n \"source\",\n \"style\",\n \"template\",\n \"track\",\n \"wbr\",\n]);\n\nfunction isInspectableLayerElement(el: HTMLElement): boolean {\n const tagName = el.tagName.toLowerCase();\n if (DOM_LAYER_IGNORED_TAGS.has(tagName)) return false;\n\n const computed = el.ownerDocument.defaultView?.getComputedStyle(el);\n if (computed?.display === \"none\" || computed?.visibility === \"hidden\") return false;\n\n return true;\n}\n\nexport function getDomLayerPatchTarget(\n el: HTMLElement,\n activeCompositionPath: string | null,\n): Pick<DomEditSelection, \"id\" | \"hfId\" | \"selector\" | \"selectorIndex\" | \"sourceFile\"> | null {\n if (!isInspectableLayerElement(el)) return null;\n if (el.hasAttribute(\"data-composition-id\")) return null;\n\n const selector = buildStableSelector(el);\n if (!selector) return null;\n\n const { sourceFile } = getSourceFileForElement(el, activeCompositionPath);\n return {\n id: el.id || undefined,\n hfId: el.getAttribute(\"data-hf-id\") || undefined,\n selector,\n selectorIndex: getSelectorIndex(\n el.ownerDocument,\n el,\n selector,\n sourceFile,\n activeCompositionPath,\n ),\n sourceFile,\n };\n}\n\n// ─── Clip ancestor / selection candidate ─────────────────────────────────────\n\nfunction getPreferredClipAncestor(startEl: HTMLElement): HTMLElement | null {\n let current: HTMLElement | null = startEl;\n while (current) {\n if (current.classList.contains(\"clip\")) {\n const isCompositionHost =\n current.hasAttribute(\"data-composition-src\") ||\n current.hasAttribute(\"data-composition-file\");\n if (!isCompositionHost || current === startEl) return current;\n }\n current = current.parentElement;\n }\n return null;\n}\n\nexport function getSelectionCandidate(\n startEl: HTMLElement,\n options: DomEditContextOptions,\n): HTMLElement {\n if (options.preferClipAncestor) {\n const clipAncestor = getPreferredClipAncestor(startEl);\n if (clipAncestor) {\n return clipAncestor;\n }\n }\n\n return startEl;\n}\n\n// ─── Visual target resolution ─────────────────────────────────────────────────\n\nexport function resolveVisualDomEditSelectionTarget(\n elementsFromPoint: Iterable<Element | null | undefined>,\n options: Pick<DomEditContextOptions, \"activeCompositionPath\">,\n): HTMLElement | null {\n const candidates = resolveAllVisualDomEditTargets(elementsFromPoint, options);\n return candidates[0] ?? null;\n}\n\n/**\n * Returns all independently-selectable elements at the given point, in paint\n * order (topmost first). Used for click-cycling through stacked layers.\n *\n * Each entry in the returned array is an independent \"layer\" — an element\n * that is not an ancestor of an earlier entry. This gives one result per\n * z-stacked element rather than one per DOM node.\n */\nexport function resolveAllVisualDomEditTargets(\n elementsFromPoint: Iterable<Element | null | undefined>,\n options: Pick<DomEditContextOptions, \"activeCompositionPath\">,\n): HTMLElement[] {\n const raw: HTMLElement[] = [];\n\n for (const entry of elementsFromPoint) {\n if (!isHtmlElement(entry)) continue;\n if (hasRenderedBox(entry) && getDomLayerPatchTarget(entry, options.activeCompositionPath)) {\n raw.push(entry);\n }\n }\n\n if (raw.length === 0) return [];\n\n // First pass: for each contiguous ancestor-descendant run, keep only the\n // deepest (most specific) element, matching the original single-pick logic.\n const layers: HTMLElement[] = [];\n let best = raw[0];\n for (let i = 1; i < raw.length; i++) {\n const el = raw[i];\n if (best.contains(el)) {\n best = el; // go deeper in this subtree\n } else {\n layers.push(best);\n best = el;\n }\n }\n layers.push(best);\n\n return layers;\n}\n\n// ─── Raster detection ────────────────────────────────────────────────────────\n\nfunction hasRasterBackground(selection: Pick<DomEditSelection, \"computedStyles\">): boolean {\n const backgroundImage = selection.computedStyles[\"background-image\"]?.trim();\n return Boolean(backgroundImage && backgroundImage !== \"none\");\n}\n\nexport function isLargeRasterDomEditSelection(\n selection: Pick<DomEditSelection, \"boundingBox\" | \"computedStyles\" | \"tagName\">,\n viewport?: DomEditViewport | null,\n): boolean {\n const tagName = selection.tagName.toLowerCase();\n const isRasterLike = tagName === \"img\" || hasRasterBackground(selection);\n if (!isRasterLike) return false;\n\n const { width, height } = selection.boundingBox;\n if (width <= 1 || height <= 1) return false;\n if (!viewport || viewport.width <= 1 || viewport.height <= 1) {\n return width >= 960 && height >= 540;\n }\n\n const areaRatio = (width * height) / (viewport.width * viewport.height);\n const widthRatio = width / viewport.width;\n const heightRatio = height / viewport.height;\n return areaRatio >= 0.4 || (widthRatio >= 0.7 && heightRatio >= 0.5);\n}\n\n// ─── Element finders ──────────────────────────────────────────────────────────\n\nexport function findElementForSelection(\n doc: Document,\n selection: Pick<DomEditSelection, \"id\" | \"hfId\" | \"selector\" | \"selectorIndex\" | \"sourceFile\">,\n activeCompositionPath: string | null = null,\n): HTMLElement | null {\n if (selection.hfId) {\n const byHfId = doc.querySelector(`[data-hf-id=\"${CSS.escape(selection.hfId)}\"]`);\n if (isHtmlElement(byHfId)) return byHfId;\n }\n\n if (selection.id) {\n const byId = doc.getElementById(selection.id);\n if (\n isHtmlElement(byId) &&\n (!selection.sourceFile ||\n getSourceFileForElement(byId, activeCompositionPath).sourceFile === selection.sourceFile)\n ) {\n return byId;\n }\n }\n\n if (!selection.selector) return null;\n\n if (selection.selector.startsWith(\".\") && selection.selectorIndex != null) {\n const matches = querySelectorAllSafely(doc, selection.selector).filter(\n (candidate): candidate is HTMLElement =>\n isHtmlElement(candidate) &&\n (!selection.sourceFile ||\n getSourceFileForElement(candidate, activeCompositionPath).sourceFile ===\n selection.sourceFile),\n );\n return matches[selection.selectorIndex] ?? null;\n }\n\n const matches = querySelectorAllSafely(doc, selection.selector).filter(\n (candidate): candidate is HTMLElement =>\n isHtmlElement(candidate) &&\n (!selection.sourceFile ||\n getSourceFileForElement(candidate, activeCompositionPath).sourceFile ===\n selection.sourceFile),\n );\n return matches[0] ?? null;\n}\n\n// fallow-ignore-next-line complexity\nexport function findElementForTimelineElement(\n doc: Document,\n element: TimelineElementDomTarget,\n options: TimelineElementDomTargetOptions,\n): HTMLElement | null {\n const elementId = typeof element.id === \"string\" ? element.id : \"\";\n const compositionSource =\n normalizeTimelineCompositionSource(element.compositionSrc) ??\n options.compIdToSrc?.get(elementId);\n const sourceFile =\n compositionSource ??\n normalizeTimelineCompositionSource(element.sourceFile) ??\n options.activeCompositionPath ??\n \"index.html\";\n const escapedElementId = escapeCssString(elementId);\n const escapedCompositionSource = compositionSource ? escapeCssString(compositionSource) : null;\n const selector =\n element.selector ??\n (compositionSource\n ? `[data-composition-src=\"${escapedCompositionSource}\"],[data-composition-file=\"${escapedCompositionSource}\"],[data-composition-id=\"${escapedElementId}\"]`\n : escapedElementId\n ? `[data-composition-id=\"${escapedElementId}\"]`\n : undefined);\n\n if (selector || element.domId) {\n const targetElement = findElementForSelection(\n doc,\n {\n id: element.domId ?? undefined,\n selector,\n selectorIndex: element.selectorIndex,\n sourceFile,\n },\n options.activeCompositionPath,\n );\n if (targetElement) return targetElement;\n }\n\n const hasExplicitDomTarget = Boolean(element.domId || element.selector || compositionSource);\n if (options.isMasterView || hasExplicitDomTarget || !options.activeCompositionPath) {\n return null;\n }\n\n const root = doc.querySelector(\"[data-composition-id]\");\n if (!isHtmlElement(root)) return null;\n return getSourceFileForElement(root, options.activeCompositionPath).sourceFile === sourceFile\n ? root\n : null;\n}\n\n// ─── Layer children ───────────────────────────────────────────────────────────\n\nexport function getDirectLayerChildren(\n el: HTMLElement,\n options: DomEditContextOptions,\n): HTMLElement[] {\n return Array.from(el.children).filter(\n (child): child is HTMLElement =>\n isHtmlElement(child) && getDomLayerPatchTarget(child, options.activeCompositionPath) !== null,\n );\n}\n","import { parsePx } from \"./domEditingDom\";\n\nconst COMPOSITION_ROOT_LAYER_EPSILON_PX = 1;\n\nfunction readPositiveDimension(value: string | null): number | null {\n if (!value) return null;\n const parsed = Number.parseFloat(value);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null;\n}\n\nfunction approximatelyEqual(a: number, b: number) {\n return Math.abs(a - b) <= COMPOSITION_ROOT_LAYER_EPSILON_PX;\n}\n\nfunction getCompositionRootBounds(doc: Document) {\n const root =\n doc.querySelector<HTMLElement>(\"[data-composition-id]\") ?? doc.documentElement ?? null;\n const rootWidth = readPositiveDimension(root?.getAttribute(\"data-width\") ?? null);\n const rootHeight = readPositiveDimension(root?.getAttribute(\"data-height\") ?? null);\n if (!root || !rootWidth || !rootHeight) return null;\n return { rect: root.getBoundingClientRect(), width: rootWidth, height: rootHeight };\n}\n\nfunction getRenderedLayerSize(element: HTMLElement, computedStyles: Record<string, string>) {\n const rect = element.getBoundingClientRect();\n const width = rect.width || parsePx(computedStyles.width);\n const height = rect.height || parsePx(computedStyles.height);\n return width && height ? { width, height } : null;\n}\n\nfunction matchesCompositionRootBounds(\n elementRect: DOMRect,\n elementSize: { width: number; height: number },\n rootBounds: { rect: DOMRect; width: number; height: number },\n) {\n return (\n approximatelyEqual(elementRect.left, rootBounds.rect.left) &&\n approximatelyEqual(elementRect.top, rootBounds.rect.top) &&\n approximatelyEqual(elementSize.width, rootBounds.width) &&\n approximatelyEqual(elementSize.height, rootBounds.height)\n );\n}\n\nfunction isExplicitFullBleedLayer(computedStyles: Record<string, string>) {\n return computedStyles.position === \"absolute\" || computedStyles.position === \"fixed\";\n}\n\nexport function isCompositionRootLayer(\n element: HTMLElement,\n doc: Document,\n computedStyles: Record<string, string>,\n) {\n if (element.parentElement !== doc.body) return false;\n if (element.hasAttribute(\"data-hf-allow-root-edit\")) return false;\n if (isExplicitFullBleedLayer(computedStyles)) return false;\n\n const rootBounds = getCompositionRootBounds(doc);\n const elementSize = getRenderedLayerSize(element, computedStyles);\n return Boolean(\n rootBounds &&\n elementSize &&\n matchesCompositionRootBounds(element.getBoundingClientRect(), elementSize, rootBounds),\n );\n}\n","/**\n * Layer items, text fields, capabilities, selection resolution, and patch operations\n * for dom editing.\n */\nimport type { PatchOperation } from \"../../utils/sourcePatcher\";\nimport type {\n DomEditCapabilities,\n DomEditContextOptions,\n DomEditLayerItem,\n DomEditSelection,\n DomEditTextField,\n} from \"./domEditingTypes\";\nimport {\n buildStableSelector,\n findClosestByAttribute,\n getCuratedComputedStyles,\n getDataAttributes,\n getInlineStyles,\n getPreferredClassSelector,\n getSelectorIndex,\n getSourceFileForElement,\n humanizeIdentifier,\n isHtmlElement,\n isIdentityTransform,\n isTextBearingTag,\n parsePx,\n} from \"./domEditingDom\";\nimport {\n findElementForSelection,\n getDomLayerPatchTarget,\n getDirectLayerChildren,\n getSelectionCandidate,\n} from \"./domEditingElement\";\nimport { isCompositionRootLayer } from \"./domEditingRootLayer\";\n\n// ─── Text fields ────────────────────────────────────────────────────────────\n\nexport function isEditableTextLeaf(el: HTMLElement): boolean {\n return isTextBearingTag(el.tagName.toLowerCase()) && el.children.length === 0;\n}\n\nfunction getTextFieldLabel(\n _tagName: string,\n index: number,\n total: number,\n source: \"self\" | \"child\",\n): string {\n if (source === \"self\" || total === 1) return \"Content\";\n return `Text ${index + 1}`;\n}\n\nfunction buildTextField(\n el: HTMLElement,\n index: number,\n total: number,\n source: \"self\" | \"child\",\n): DomEditTextField {\n const tagName = el.tagName.toLowerCase();\n const key = el.getAttribute(\"data-hf-text-key\") ?? `${source}:${index}:${tagName}`;\n return {\n key,\n label: getTextFieldLabel(tagName, index, total, source),\n value: el.textContent ?? \"\",\n tagName,\n attributes: Array.from(el.attributes)\n .filter((attribute) => attribute.name !== \"style\")\n .map((attribute) => ({\n name: attribute.name,\n value: attribute.value,\n })),\n inlineStyles: getInlineStyles(el),\n computedStyles: getCuratedComputedStyles(el),\n source,\n };\n}\n\n// fallow-ignore-next-line complexity\nexport function collectDomEditTextFields(el: HTMLElement): DomEditTextField[] {\n const childElements = Array.from(el.children).filter(isHtmlElement).filter(isEditableTextLeaf);\n\n if (childElements.length > 0) {\n const hasMixedContent = Array.from(el.childNodes).some(\n (node) => node.nodeType === 3 && node.textContent?.trim(),\n );\n\n if (hasMixedContent) {\n const fields: DomEditTextField[] = [];\n let childIdx = 0;\n for (const node of el.childNodes) {\n if (node.nodeType === 3) {\n const text = node.textContent ?? \"\";\n if (!text.trim()) continue;\n fields.push({\n key: `text-node:${childIdx}`,\n label: `Text ${childIdx + 1}`,\n value: text,\n tagName: \"#text\",\n attributes: [],\n inlineStyles: {},\n computedStyles: {},\n source: \"text-node\",\n });\n childIdx++;\n } else if (isHtmlElement(node) && isEditableTextLeaf(node)) {\n fields.push(buildTextField(node, childIdx, childElements.length, \"child\"));\n childIdx++;\n }\n }\n return fields;\n }\n\n return childElements.map((child, index) =>\n buildTextField(child, index, childElements.length, \"child\"),\n );\n }\n\n if (isEditableTextLeaf(el)) {\n return [buildTextField(el, 0, 1, \"self\")];\n }\n\n return [];\n}\n\nfunction escapeHtmlText(value: string): string {\n return value.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n}\n\nfunction serializeTextFieldStyle(field: DomEditTextField): string {\n const entries = Object.entries(field.inlineStyles).filter(([, value]) => Boolean(value));\n if (entries.length === 0) return \"\";\n return entries.map(([key, value]) => `${key}: ${value}`).join(\"; \");\n}\n\nexport function serializeDomEditTextFields(fields: DomEditTextField[]): string {\n return fields\n .filter((field) => field.source === \"child\" || field.source === \"text-node\")\n .map((field) => {\n if (field.source === \"text-node\") {\n return escapeHtmlText(field.value);\n }\n const attrs = [\n ...field.attributes.filter((attribute) => attribute.name !== \"data-hf-text-key\"),\n { name: \"data-hf-text-key\", value: field.key },\n ]\n .map((attribute) => ` ${attribute.name}=\"${attribute.value.replace(/\"/g, \""\")}\"`)\n .join(\"\");\n const style = serializeTextFieldStyle(field);\n const styleAttr = style ? ` style=\"${style.replace(/\"/g, \""\")}\"` : \"\";\n return `<${field.tagName}${attrs}${styleAttr}>${escapeHtmlText(field.value)}</${field.tagName}>`;\n })\n .join(\"\");\n}\n\nexport function buildDefaultDomEditTextField(base?: Partial<DomEditTextField>): DomEditTextField {\n return {\n key: `child:new:${Date.now()}`,\n label: \"Text\",\n value: \"New text\",\n tagName: \"span\",\n attributes: [],\n inlineStyles: {\n \"font-family\": base?.computedStyles?.[\"font-family\"] ?? \"inherit\",\n \"font-size\": base?.computedStyles?.[\"font-size\"] ?? \"16px\",\n \"font-weight\": base?.computedStyles?.[\"font-weight\"] ?? \"400\",\n color: base?.computedStyles?.color ?? \"inherit\",\n },\n computedStyles: {},\n source: \"child\",\n };\n}\n\n// ─── Capabilities ────────────────────────────────────────────────────────────\n\n// fallow-ignore-next-line complexity\nexport function resolveDomEditCapabilities(args: {\n selector?: string;\n hfId?: string;\n tagName?: string;\n className?: string;\n inlineStyles: Record<string, string>;\n computedStyles: Record<string, string>;\n isCompositionHost: boolean;\n isCompositionRoot?: boolean;\n isInsideLockedComposition: boolean;\n isMasterView: boolean;\n existsInSource?: boolean;\n}): DomEditCapabilities {\n if ((!args.selector && !args.hfId) || args.isInsideLockedComposition) {\n return {\n canSelect: !args.isInsideLockedComposition,\n canEditStyles: false,\n canMove: false,\n canResize: false,\n canApplyManualOffset: false,\n canApplyManualSize: false,\n canApplyManualRotation: false,\n reasonIfDisabled: args.isInsideLockedComposition\n ? \"This element belongs to a locked composition.\"\n : \"Studio could not resolve a stable patch target for this element.\",\n };\n }\n\n if (args.existsInSource === false) {\n return {\n canSelect: true,\n canEditStyles: false,\n canMove: false,\n canResize: false,\n canApplyManualOffset: false,\n canApplyManualSize: false,\n canApplyManualRotation: false,\n reasonIfDisabled: \"This element is generated by a script and cannot be edited visually.\",\n };\n }\n\n if (args.isCompositionRoot) {\n return {\n canSelect: true,\n canEditStyles: true,\n canMove: false,\n canResize: false,\n canApplyManualOffset: false,\n canApplyManualSize: false,\n canApplyManualRotation: false,\n reasonIfDisabled: \"The root composition defines the preview bounds.\",\n };\n }\n\n const position = args.computedStyles.position;\n const left = parsePx(args.inlineStyles.left) ?? parsePx(args.computedStyles.left);\n const top = parsePx(args.inlineStyles.top) ?? parsePx(args.computedStyles.top);\n const width = parsePx(args.inlineStyles.width) ?? parsePx(args.computedStyles.width);\n const height = parsePx(args.inlineStyles.height) ?? parsePx(args.computedStyles.height);\n const hasTransformDrivenGeometry = !isIdentityTransform(args.computedStyles.transform);\n\n const canMove =\n (position === \"absolute\" || position === \"fixed\") &&\n left != null &&\n top != null &&\n !hasTransformDrivenGeometry;\n\n const canResize = canMove && (width != null || height != null);\n const canApplyManualGeometry = !args.isCompositionHost;\n const canApplyManualOffset = canApplyManualGeometry;\n const canApplyManualSize = canApplyManualGeometry;\n const canApplyManualRotation = canApplyManualGeometry;\n const reasonIfDisabled = canApplyManualGeometry\n ? undefined\n : \"Select an internal layer to transform it.\";\n\n if (args.isCompositionHost && args.isMasterView) {\n return {\n canSelect: true,\n canEditStyles: false,\n canMove,\n canResize,\n canApplyManualOffset,\n canApplyManualSize,\n canApplyManualRotation,\n reasonIfDisabled,\n };\n }\n\n return {\n canSelect: true,\n canEditStyles: true,\n canMove,\n canResize,\n canApplyManualOffset,\n canApplyManualSize,\n canApplyManualRotation,\n reasonIfDisabled,\n };\n}\n\n// ─── Element label ────────────────────────────────────────────────────────────\n\n// fallow-ignore-next-line complexity\nexport function buildElementLabel(el: HTMLElement): string {\n const compositionId = el.getAttribute(\"data-composition-id\");\n if (compositionId && compositionId !== \"main\") {\n return humanizeIdentifier(compositionId);\n }\n\n const compositionSrc =\n el.getAttribute(\"data-composition-src\") ?? el.getAttribute(\"data-composition-file\");\n if (compositionSrc) {\n return humanizeIdentifier(compositionSrc);\n }\n\n if (el.id) return humanizeIdentifier(el.id);\n\n const preferredClass = getPreferredClassSelector(el);\n if (preferredClass) {\n return humanizeIdentifier(preferredClass.replace(/^\\./, \"\"));\n }\n\n const text = (el.textContent ?? \"\").trim().replace(/\\s+/g, \" \");\n if (text) return text.length > 40 ? `${text.slice(0, 39)}…` : text;\n return el.tagName.toLowerCase();\n}\n\n// ─── Source probe ────────────────────────────────────────────────────────────\n\nasync function probeSourceElement(\n projectId: string,\n sourceFile: string,\n target: { id?: string; hfId?: string; selector?: string; selectorIndex?: number },\n): Promise<boolean> {\n try {\n const response = await fetch(\n `/api/projects/${projectId}/file-mutations/probe-element/${encodeURIComponent(sourceFile)}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ target }),\n },\n );\n if (!response.ok) return true;\n const data = (await response.json()) as { exists?: boolean };\n return data.exists !== false;\n } catch {\n return true;\n }\n}\n\n// ─── Selection resolution ────────────────────────────────────────────────────\n\n// fallow-ignore-next-line complexity\nexport async function resolveDomEditSelection(\n startEl: HTMLElement | null,\n options: DomEditContextOptions & { projectId?: string | null; skipSourceProbe?: boolean },\n): Promise<DomEditSelection | null> {\n if (!startEl) return null;\n const doc = startEl.ownerDocument;\n\n let current: HTMLElement | null = getSelectionCandidate(startEl, options);\n while (current && current !== doc.body && current !== doc.documentElement) {\n const selector = buildStableSelector(current);\n const hfId = readHfId(current);\n if (!selector && !hfId) {\n current = current.parentElement;\n continue;\n }\n\n const { sourceFile, compositionPath } = getSourceFileForElement(\n current,\n options.activeCompositionPath,\n );\n const selectorIndex = selector\n ? getSelectorIndex(doc, current, selector, sourceFile, options.activeCompositionPath)\n : undefined;\n const compositionSrc =\n current.getAttribute(\"data-composition-src\") ??\n current.getAttribute(\"data-composition-file\") ??\n undefined;\n const inlineStyles = getInlineStyles(current);\n const computedStyles = getCuratedComputedStyles(current);\n const isCompositionRoot =\n (current.hasAttribute(\"data-composition-id\") && !compositionSrc) ||\n isCompositionRootLayer(current, doc, computedStyles);\n const textFields = collectDomEditTextFields(current);\n const isInsideLocked = Boolean(findClosestByAttribute(current, [\"data-timeline-locked\"]));\n let existsInSource: boolean | undefined;\n if (!options.skipSourceProbe && options.projectId && (current.id || selector || hfId)) {\n const probeTarget: { id?: string; hfId?: string; selector?: string; selectorIndex?: number } =\n {};\n if (current.id) probeTarget.id = current.id;\n if (hfId) probeTarget.hfId = hfId;\n if (selector) probeTarget.selector = selector;\n if (selectorIndex != null) probeTarget.selectorIndex = selectorIndex;\n existsInSource = await probeSourceElement(options.projectId, sourceFile, probeTarget);\n }\n const capabilities = resolveDomEditCapabilities({\n selector,\n hfId,\n tagName: current.tagName.toLowerCase(),\n className: current.className,\n inlineStyles,\n computedStyles,\n isCompositionHost: Boolean(compositionSrc),\n isCompositionRoot,\n isInsideLockedComposition: isInsideLocked,\n isMasterView: options.isMasterView,\n existsInSource,\n });\n const rect = current.getBoundingClientRect();\n\n return {\n element: current,\n id: current.id || undefined,\n hfId,\n selector,\n selectorIndex,\n sourceFile,\n compositionPath,\n compositionSrc,\n isCompositionHost: Boolean(compositionSrc),\n isInsideLockedComposition: isInsideLocked,\n label: buildElementLabel(current),\n tagName: current.tagName.toLowerCase(),\n boundingBox: {\n x: rect.left,\n y: rect.top,\n width: rect.width,\n height: rect.height,\n },\n textContent: current.textContent?.trim() || null,\n dataAttributes: getDataAttributes(current),\n inlineStyles,\n computedStyles,\n textFields,\n capabilities,\n };\n }\n\n return null;\n}\n\nexport async function refreshDomEditSelection(\n selection: DomEditSelection,\n activeCompositionPath: string | null,\n): Promise<DomEditSelection | null> {\n const doc = selection.element.ownerDocument;\n const nextElement = findElementForSelection(doc, selection, activeCompositionPath);\n return nextElement\n ? resolveDomEditSelection(nextElement, {\n activeCompositionPath,\n isMasterView: !activeCompositionPath || activeCompositionPath === \"index.html\",\n })\n : null;\n}\n\n// ─── Layer items ─────────────────────────────────────────────────────────────\n\nexport function getDomEditLayerKey(\n target: Pick<DomEditSelection, \"id\" | \"selector\" | \"selectorIndex\" | \"sourceFile\">,\n): string {\n const selectorIndex = target.selectorIndex ?? 0;\n return `${target.sourceFile}:${target.id ?? target.selector ?? \"layer\"}:${selectorIndex}`;\n}\n\nexport function countDomEditChildLayers(\n root: HTMLElement | null | undefined,\n options: DomEditContextOptions,\n maxCount = 99,\n): number {\n if (!root) return 0;\n\n let count = 0;\n const visit = (el: HTMLElement) => {\n for (const child of Array.from(el.children)) {\n if (!isHtmlElement(child)) continue;\n if (getDomLayerPatchTarget(child, options.activeCompositionPath)) {\n count += 1;\n if (count >= maxCount) return;\n }\n visit(child);\n if (count >= maxCount) return;\n }\n };\n\n visit(root);\n return count;\n}\n\nexport function collectDomEditLayerItems(\n root: HTMLElement | null | undefined,\n options: DomEditContextOptions,\n maxItems = 80,\n): DomEditLayerItem[] {\n if (!root) return [];\n\n const items: DomEditLayerItem[] = [];\n // fallow-ignore-next-line complexity\n const visit = (el: HTMLElement, depth: number) => {\n if (items.length >= maxItems) return;\n\n const target = getDomLayerPatchTarget(el, options.activeCompositionPath);\n if (target) {\n items.push({\n key: getDomEditLayerKey(target),\n element: el,\n label: buildElementLabel(el),\n tagName: el.tagName.toLowerCase(),\n depth,\n childCount: getDirectLayerChildren(el, options).length,\n id: target.id ?? undefined,\n hfId: target.hfId ?? undefined,\n selector: target.selector ?? undefined,\n selectorIndex: target.selectorIndex,\n sourceFile: target.sourceFile,\n });\n }\n\n const nextDepth = target ? depth + 1 : depth;\n for (const child of Array.from(el.children)) {\n if (!isHtmlElement(child)) continue;\n visit(child, nextDepth);\n if (items.length >= maxItems) return;\n }\n };\n\n visit(root, 0);\n return items;\n}\n\n// ─── Patch operations ────────────────────────────────────────────────────────\n\nexport function buildDomEditStylePatchOperation(property: string, value: string): PatchOperation {\n return {\n type: \"inline-style\",\n property,\n value,\n };\n}\n\nexport function buildDomEditTextPatchOperation(value: string): PatchOperation {\n return {\n type: \"text-content\",\n property: \"text\",\n value,\n };\n}\n\n// ─── Non-editable reason ─────────────────────────────────────────────────────\n\nfunction hasSupportedDirectEdit(capabilities: DomEditCapabilities): boolean {\n return (\n capabilities.canEditStyles ||\n capabilities.canMove ||\n capabilities.canResize ||\n capabilities.canApplyManualOffset ||\n capabilities.canApplyManualSize ||\n capabilities.canApplyManualRotation\n );\n}\n\nexport function getDomEditNonEditableReason(\n element: HTMLElement,\n selection: DomEditSelection | null,\n): string | null {\n if (!selection) {\n return \"No stable source target\";\n }\n\n if (selection.element !== element) {\n return selection.isCompositionHost\n ? \"Nested composition boundary\"\n : `Selection resolves to ${selection.label}`;\n }\n\n if (!hasSupportedDirectEdit(selection.capabilities)) {\n return selection.capabilities.reasonIfDisabled ?? \"No supported direct edits\";\n }\n\n return null;\n}\n\nexport function getDomEditTargetKey(\n selection: Pick<DomEditSelection, \"id\" | \"hfId\" | \"selector\" | \"selectorIndex\" | \"sourceFile\">,\n): string {\n return [\n selection.sourceFile || \"index.html\",\n selection.hfId ?? \"\",\n selection.id ?? \"\",\n selection.selector ?? \"\",\n selection.selectorIndex ?? \"\",\n ].join(\"|\");\n}\n\nexport function isTextEditableSelection(selection: DomEditSelection): boolean {\n return (\n selection.textFields.length > 0 &&\n !selection.isCompositionHost &&\n !selection.isInsideLockedComposition\n );\n}\n\n// buildElementAgentPrompt is in domEditingAgentPrompt.ts\n\nexport function readHfId(element: Element): string | undefined {\n return element.getAttribute(\"data-hf-id\")?.trim() || undefined;\n}\n\nexport function buildDomEditPatchTarget(\n selection: Pick<DomEditSelection, \"id\" | \"hfId\" | \"selector\" | \"selectorIndex\">,\n): { id?: string | null; hfId?: string; selector?: string; selectorIndex?: number } {\n return {\n id: selection.id,\n hfId: selection.hfId,\n selector: selection.selector,\n selectorIndex: selection.selectorIndex,\n };\n}\n"],"mappings":"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","names":["values","matches"]}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { pickKeyframeTween, computeKeyframeMovePlan } from "./keyframeMove";
|
|
3
|
-
|
|
4
|
-
const flat = (id: string, target: string, position: number, duration: number, group?: string) => ({
|
|
5
|
-
id,
|
|
6
|
-
targetSelector: target,
|
|
7
|
-
position,
|
|
8
|
-
duration,
|
|
9
|
-
resolvedStart: position,
|
|
10
|
-
propertyGroup: group,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const el = { start: 0, duration: 10, domId: "box", selector: "#box" };
|
|
14
|
-
|
|
15
|
-
describe("pickKeyframeTween", () => {
|
|
16
|
-
it("matches by the element's selector", () => {
|
|
17
|
-
const anims = [flat("a", "#other", 0, 5), flat("b", "#box", 2, 3)];
|
|
18
|
-
expect(pickKeyframeTween(anims, el, 3, undefined)?.id).toBe("b");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("prefers the dragged keyframe's property group", () => {
|
|
22
|
-
const anims = [flat("pos", "#box", 0, 8, "position"), flat("vis", "#box", 0, 8, "visual")];
|
|
23
|
-
expect(pickKeyframeTween(anims, el, 1, "visual")?.id).toBe("vis");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("among same-group tweens picks the one whose window contains the original time", () => {
|
|
27
|
-
const fadeIn = flat("in", "#box", 1, 1, "visual");
|
|
28
|
-
const fadeOut = flat("out", "#box", 8, 1, "visual");
|
|
29
|
-
expect(pickKeyframeTween([fadeIn, fadeOut], el, 8.5, "visual")?.id).toBe("out");
|
|
30
|
-
expect(pickKeyframeTween([fadeIn, fadeOut], el, 1.2, "visual")?.id).toBe("in");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("returns undefined when there are no tweens", () => {
|
|
34
|
-
expect(pickKeyframeTween([], el, 1, undefined)).toBeUndefined();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("returns undefined rather than editing another element on a selector mismatch", () => {
|
|
38
|
-
const anims = [flat("a", "#other", 0, 5), flat("b", ".unrelated", 2, 3)];
|
|
39
|
-
expect(pickKeyframeTween(anims, el, 3, undefined)).toBeUndefined();
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe("computeKeyframeMovePlan — flat tween", () => {
|
|
44
|
-
const anim = flat("t", "#box", 2, 4); // window [2, 6]
|
|
45
|
-
|
|
46
|
-
it("start point trims the front, keeping the end fixed", () => {
|
|
47
|
-
// newPct 30% → abs 3 → start moves to 3, duration shrinks to 3.
|
|
48
|
-
const plan = computeKeyframeMovePlan(anim, 0, el, 30);
|
|
49
|
-
expect(plan.meta).toEqual({ position: 3, duration: 3 });
|
|
50
|
-
expect(plan.removes).toEqual([]);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("end point resizes, keeping the start", () => {
|
|
54
|
-
// tweenOldPct 100 (end) → newPct 80% → abs 8 → duration 6, start unchanged.
|
|
55
|
-
const plan = computeKeyframeMovePlan(anim, 100, el, 80);
|
|
56
|
-
expect(plan.meta).toEqual({ position: 2, duration: 6 });
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe("computeKeyframeMovePlan — keyframe-array tween", () => {
|
|
61
|
-
const anim = {
|
|
62
|
-
id: "k",
|
|
63
|
-
targetSelector: "#box",
|
|
64
|
-
position: 0,
|
|
65
|
-
duration: 10,
|
|
66
|
-
resolvedStart: 0,
|
|
67
|
-
keyframes: {
|
|
68
|
-
keyframes: [
|
|
69
|
-
{ percentage: 0, properties: { x: 0 } },
|
|
70
|
-
{ percentage: 50, properties: { x: 50 } },
|
|
71
|
-
{ percentage: 100, properties: { x: 100 } },
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
it("moves an intermediate keyframe without touching the tween or others", () => {
|
|
77
|
-
// mid keyframe (tweenPct 50) → newPct 70% → abs 7 → 70% of the tween.
|
|
78
|
-
const plan = computeKeyframeMovePlan(anim, 50, el, 70);
|
|
79
|
-
expect(plan.meta).toBeUndefined();
|
|
80
|
-
expect(plan.removes).toEqual([50]);
|
|
81
|
-
expect(plan.adds).toEqual([{ pct: 70, properties: { x: 50 } }]);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("start move remaps intermediates to preserve their absolute times", () => {
|
|
85
|
-
// start (tweenPct 0) → newPct 20% → abs 2 → window [2,10]. The 50% keyframe
|
|
86
|
-
// was at abs 5 → now (5-2)/8 = 37.5%.
|
|
87
|
-
const plan = computeKeyframeMovePlan(anim, 0, el, 20);
|
|
88
|
-
expect(plan.meta).toEqual({ position: 2, duration: 8 });
|
|
89
|
-
expect(plan.removes).toContain(50);
|
|
90
|
-
const mid = plan.adds.find((a) => a.properties.x === 50);
|
|
91
|
-
expect(mid?.pct).toBeCloseTo(37.5, 1);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("is a no-op when the dragged keyframe can't be located (stale cache)", () => {
|
|
95
|
-
// tweenOldPct 33 matches no keyframe (0/50/100) → must NOT resize the tween.
|
|
96
|
-
const plan = computeKeyframeMovePlan(anim, 33, el, 70);
|
|
97
|
-
expect(plan.meta).toBeUndefined();
|
|
98
|
-
expect(plan.removes).toEqual([]);
|
|
99
|
-
expect(plan.adds).toEqual([]);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure helpers for committing a keyframe-diamond drag: pick the tween the
|
|
3
|
-
* dragged keyframe belongs to, and compute the GSAP mutations (tween
|
|
4
|
-
* position/duration and/or keyframe add/remove) for the move. Kept free of
|
|
5
|
-
* React/store so the timeline drag handler stays a thin orchestrator.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
interface TweenLike {
|
|
9
|
-
id: string;
|
|
10
|
-
targetSelector: string;
|
|
11
|
-
position: number | string;
|
|
12
|
-
duration?: number;
|
|
13
|
-
resolvedStart?: number;
|
|
14
|
-
propertyGroup?: string;
|
|
15
|
-
keyframes?: { keyframes: { percentage: number; properties: Record<string, number | string> }[] };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface ElementWindow {
|
|
19
|
-
start: number;
|
|
20
|
-
duration: number;
|
|
21
|
-
domId?: string;
|
|
22
|
-
selector?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface KeyframeMovePlan {
|
|
26
|
-
/** Tween timing change (start/end point drags). */
|
|
27
|
-
meta?: { position: number; duration: number };
|
|
28
|
-
/** Keyframe percentages to remove, then re-add (intermediate move / remap). */
|
|
29
|
-
removes: number[];
|
|
30
|
-
adds: { pct: number; properties: Record<string, number | string> }[];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const round3 = (n: number) => Math.round(n * 1000) / 1000;
|
|
34
|
-
const clampPct = (n: number) => Math.max(0, Math.min(100, Math.round(n * 100) / 100));
|
|
35
|
-
const MIN_DUR = 0.05;
|
|
36
|
-
|
|
37
|
-
function tweenWindow(a: TweenLike): { start: number; dur: number } {
|
|
38
|
-
return {
|
|
39
|
-
start: a.resolvedStart ?? (typeof a.position === "number" ? a.position : 0),
|
|
40
|
-
dur: a.duration ?? 0,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
type Kf = { percentage: number; properties: Record<string, number | string> };
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Remap every keyframe except `keepIdx` from the old tween window to the new one
|
|
48
|
-
* so their absolute times stay fixed after a start/end resize. Returns the
|
|
49
|
-
* remove/add ops (empty for flat tweens, which have no intermediates).
|
|
50
|
-
*/
|
|
51
|
-
function remapKeyframes(
|
|
52
|
-
kfs: Kf[],
|
|
53
|
-
keepIdx: number,
|
|
54
|
-
oldStart: number,
|
|
55
|
-
oldDur: number,
|
|
56
|
-
newStart: number,
|
|
57
|
-
newDur: number,
|
|
58
|
-
): Pick<KeyframeMovePlan, "removes" | "adds"> {
|
|
59
|
-
const removes: number[] = [];
|
|
60
|
-
const adds: KeyframeMovePlan["adds"] = [];
|
|
61
|
-
if (newDur <= 0) return { removes, adds };
|
|
62
|
-
for (let i = 0; i < kfs.length; i++) {
|
|
63
|
-
if (i === keepIdx) continue;
|
|
64
|
-
const k = kfs[i]!;
|
|
65
|
-
const absT = oldStart + (k.percentage / 100) * oldDur;
|
|
66
|
-
const remapped = clampPct(((absT - newStart) / newDur) * 100);
|
|
67
|
-
if (Math.abs(remapped - k.percentage) < 0.05) continue;
|
|
68
|
-
removes.push(k.percentage);
|
|
69
|
-
adds.push({ pct: remapped, properties: k.properties });
|
|
70
|
-
}
|
|
71
|
-
return { removes, adds };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Pick the tween the dragged keyframe belongs to: restrict to the element's
|
|
76
|
-
* selector and (if known) the keyframe's property group, then choose the one
|
|
77
|
-
* whose time window contains — or is nearest — the keyframe's original time.
|
|
78
|
-
* An element can have several tweens in one group (e.g. fade-in + fade-out).
|
|
79
|
-
*/
|
|
80
|
-
export function pickKeyframeTween<T extends TweenLike>(
|
|
81
|
-
anims: T[],
|
|
82
|
-
el: ElementWindow,
|
|
83
|
-
origAbsTime: number,
|
|
84
|
-
group: string | undefined,
|
|
85
|
-
): T | undefined {
|
|
86
|
-
const selectors = [el.domId ? `#${el.domId}` : null, el.selector].filter(Boolean);
|
|
87
|
-
const forEl = anims.filter((a) => selectors.includes(a.targetSelector));
|
|
88
|
-
// Only ever pick among THIS element's tweens. Don't fall back to all
|
|
89
|
-
// animations — a selector mismatch (e.g. a class/compound-selector tween)
|
|
90
|
-
// would otherwise edit a different element's keyframes. No match → no-op.
|
|
91
|
-
if (forEl.length === 0) return undefined;
|
|
92
|
-
const groupPool = group ? forEl.filter((a) => a.propertyGroup === group) : [];
|
|
93
|
-
const candidates = groupPool.length > 0 ? groupPool : forEl;
|
|
94
|
-
const dist = (a: T): number => {
|
|
95
|
-
const { start, dur } = tweenWindow(a);
|
|
96
|
-
if (origAbsTime >= start && origAbsTime <= start + dur) return 0;
|
|
97
|
-
return Math.min(Math.abs(origAbsTime - start), Math.abs(origAbsTime - (start + dur)));
|
|
98
|
-
};
|
|
99
|
-
return candidates.reduce((best, a) => (dist(a) < dist(best) ? a : best), candidates[0]!);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Compute the mutations for moving a keyframe to `newPct` (clip-relative):
|
|
104
|
-
* - start point → trim front (position moves, end fixed),
|
|
105
|
-
* - end point → resize (duration changes, start fixed),
|
|
106
|
-
* - intermediate → move only that keyframe; start/end moves remap the other
|
|
107
|
-
* keyframes so their absolute times stay put.
|
|
108
|
-
*/
|
|
109
|
-
// fallow-ignore-next-line complexity
|
|
110
|
-
export function computeKeyframeMovePlan(
|
|
111
|
-
anim: TweenLike,
|
|
112
|
-
tweenOldPct: number,
|
|
113
|
-
el: ElementWindow,
|
|
114
|
-
newPct: number,
|
|
115
|
-
): KeyframeMovePlan {
|
|
116
|
-
const newAbsTime = el.start + (newPct / 100) * el.duration;
|
|
117
|
-
const tweenStart = tweenWindow(anim).start;
|
|
118
|
-
const tweenDur = anim.duration ?? el.duration;
|
|
119
|
-
const kfs = anim.keyframes
|
|
120
|
-
? anim.keyframes.keyframes.slice().sort((a, b) => a.percentage - b.percentage)
|
|
121
|
-
: null;
|
|
122
|
-
const idx = kfs ? kfs.findIndex((k) => Math.abs(k.percentage - tweenOldPct) < 0.5) : -1;
|
|
123
|
-
|
|
124
|
-
// Keyframe-array tween but the dragged keyframe couldn't be located (stale
|
|
125
|
-
// cache / precision drift): no-op rather than falling through to an end-point
|
|
126
|
-
// resize that would silently rescale the whole tween and re-time every key.
|
|
127
|
-
if (kfs && idx === -1) return { removes: [], adds: [] };
|
|
128
|
-
|
|
129
|
-
if (kfs && idx > 0 && idx < kfs.length - 1) {
|
|
130
|
-
const movedPct = tweenDur > 0 ? clampPct(((newAbsTime - tweenStart) / tweenDur) * 100) : 0;
|
|
131
|
-
return { removes: [tweenOldPct], adds: [{ pct: movedPct, properties: kfs[idx]!.properties }] };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const isStartPoint = kfs ? idx === 0 : tweenOldPct <= 50;
|
|
135
|
-
let newStart = tweenStart;
|
|
136
|
-
let newDur = tweenDur;
|
|
137
|
-
if (isStartPoint) {
|
|
138
|
-
const end = tweenStart + tweenDur;
|
|
139
|
-
newStart = Math.max(0, Math.min(newAbsTime, end - MIN_DUR));
|
|
140
|
-
newDur = end - newStart;
|
|
141
|
-
} else {
|
|
142
|
-
newDur = Math.max(MIN_DUR, newAbsTime - tweenStart);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const windowChanged = newStart !== tweenStart || newDur !== tweenDur;
|
|
146
|
-
const remap =
|
|
147
|
-
kfs && windowChanged
|
|
148
|
-
? remapKeyframes(kfs, idx, tweenStart, tweenDur, newStart, newDur)
|
|
149
|
-
: { removes: [], adds: [] };
|
|
150
|
-
return { meta: { position: round3(newStart), duration: round3(newDur) }, ...remap };
|
|
151
|
-
}
|
|
File without changes
|