@kokoa/clotho-editor 0.2.0 → 0.3.0

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.
@@ -1096,6 +1096,227 @@ body.studio-player-popout #clotho-player-preview {
1096
1096
  color: var(--color-accent);
1097
1097
  background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.15));
1098
1098
  }
1099
+ /* Canvas overlay for the camera. Drawn into the editing SVG, which keeps the
1100
+ full-canvas viewBox, so the frame is the part a reader will actually see. */
1101
+ .studio-camera-frame-scrim {
1102
+ /* Fixed rather than themed: this dims the *document's* canvas, which keeps its
1103
+ own background whatever the editor chrome is doing. A themed value goes
1104
+ invisible exactly when the editor is dark and the canvas is white. */
1105
+ fill: rgba(15, 23, 42, 0.32);
1106
+ }
1107
+ .studio-camera-frame-rect {
1108
+ fill: none;
1109
+ stroke: #7c3aed;
1110
+ stroke-width: 2;
1111
+ stroke-dasharray: 6 4;
1112
+ }
1113
+ .studio-camera-frame.is-selected .studio-camera-frame-rect {
1114
+ stroke-dasharray: none;
1115
+ stroke-width: 2.5;
1116
+ }
1117
+ .studio-camera-frame-rect[data-camera-frame] {
1118
+ cursor: pointer;
1119
+ }
1120
+ /* Transparent, but it takes the pan drag — so it needs a fill to be hit. */
1121
+ .studio-camera-frame-body {
1122
+ fill: transparent;
1123
+ cursor: grab;
1124
+ }
1125
+ .studio-camera-frame-body:active {
1126
+ cursor: grabbing;
1127
+ }
1128
+ .studio-camera-frame-handle {
1129
+ fill: var(--color-surface-elevated, #fff);
1130
+ stroke: #7c3aed;
1131
+ stroke-width: 1.5;
1132
+ cursor: nwse-resize;
1133
+ }
1134
+ .studio-camera-frame-handle:hover {
1135
+ fill: #7c3aed;
1136
+ }
1137
+ .studio-camera-frame-label {
1138
+ fill: #7c3aed;
1139
+ font-family: var(--font-mono);
1140
+ font-size: 12px;
1141
+ }
1142
+ .studio-camera-frame-warning {
1143
+ fill: #ef4444;
1144
+ font-family: var(--font-mono);
1145
+ font-size: 11px;
1146
+ }
1147
+ .studio-camera-readout {
1148
+ padding: 0.4rem 0.55rem;
1149
+ margin-bottom: 0.4rem;
1150
+ border-radius: 6px;
1151
+ background-color: var(--color-surface);
1152
+ font-family: var(--font-mono);
1153
+ font-size: 0.7rem;
1154
+ color: var(--color-fg-muted);
1155
+ }
1156
+ .studio-camera-issue {
1157
+ color: #ef4444;
1158
+ }
1159
+ .studio-camera-focus > summary {
1160
+ cursor: pointer;
1161
+ padding: 0.3rem 0;
1162
+ font-family: var(--font-mono);
1163
+ font-size: 0.7rem;
1164
+ }
1165
+ .studio-camera-track {
1166
+ padding: 0.3rem 0;
1167
+ border-top: 1px solid var(--color-border);
1168
+ }
1169
+ .studio-camera-track-head {
1170
+ display: flex;
1171
+ align-items: center;
1172
+ gap: 0.4rem;
1173
+ font-family: var(--font-mono);
1174
+ font-size: 0.72rem;
1175
+ }
1176
+ .studio-camera-track-now {
1177
+ margin-left: auto;
1178
+ color: var(--color-fg-muted);
1179
+ font-size: 0.68rem;
1180
+ }
1181
+ .studio-camera-track .studio-props-empty {
1182
+ margin: 0.2rem 0 0;
1183
+ padding: 0;
1184
+ text-align: left;
1185
+ font-size: 0.68rem;
1186
+ }
1187
+ .studio-camera-control {
1188
+ color: #7c3aed;
1189
+ }
1190
+ .studio-camera-field-label {
1191
+ margin: 0.5rem 0 0.25rem;
1192
+ font-size: 0.72rem;
1193
+ color: var(--color-fg-muted);
1194
+ }
1195
+ .studio-camera-range {
1196
+ margin: 0.45rem 0;
1197
+ }
1198
+ .studio-camera-range-head {
1199
+ display: flex;
1200
+ justify-content: space-between;
1201
+ align-items: baseline;
1202
+ font-size: 0.72rem;
1203
+ color: var(--color-fg-muted);
1204
+ }
1205
+ .studio-camera-range-value {
1206
+ font-family: var(--font-mono);
1207
+ color: var(--color-fg);
1208
+ }
1209
+ .studio-camera-range-row {
1210
+ display: flex;
1211
+ align-items: center;
1212
+ gap: 0.4rem;
1213
+ }
1214
+ .studio-camera-range-row input[type="range"] {
1215
+ flex: 1;
1216
+ min-width: 0;
1217
+ }
1218
+ .studio-camera-range-row input[type="number"] {
1219
+ width: 5.2rem;
1220
+ flex: 0 0 auto;
1221
+ }
1222
+ .studio-camera-hint {
1223
+ margin: 0.2rem 0 0;
1224
+ font-size: 0.66rem;
1225
+ color: var(--color-fg-muted);
1226
+ }
1227
+ .studio-camera-chips {
1228
+ display: flex;
1229
+ flex-wrap: wrap;
1230
+ gap: 0.25rem;
1231
+ margin: 0.25rem 0;
1232
+ }
1233
+ .studio-chip {
1234
+ padding: 0.1rem 0.45rem;
1235
+ border: 1px solid var(--color-border);
1236
+ border-radius: 999px;
1237
+ background: none;
1238
+ color: var(--color-fg-muted);
1239
+ font: inherit;
1240
+ font-size: 0.68rem;
1241
+ cursor: pointer;
1242
+ }
1243
+ .studio-chip.is-on {
1244
+ border-color: #7c3aed;
1245
+ background-color: color-mix(in srgb, #7c3aed 18%, transparent);
1246
+ color: var(--color-fg);
1247
+ }
1248
+ .studio-camera-focus-actions {
1249
+ display: flex;
1250
+ flex-wrap: wrap;
1251
+ gap: 0.3rem;
1252
+ margin: 0.3rem 0;
1253
+ }
1254
+ /* The target list scrolls: a document with forty elements must not push the
1255
+ padding and maxZoom controls off the panel. */
1256
+ .studio-camera-targets {
1257
+ max-height: 11rem;
1258
+ overflow-y: auto;
1259
+ border: 1px solid var(--color-border);
1260
+ border-radius: 6px;
1261
+ padding: 0.2rem;
1262
+ }
1263
+ .studio-camera-target {
1264
+ display: flex;
1265
+ align-items: center;
1266
+ gap: 0.35rem;
1267
+ padding: 0.15rem 0.3rem;
1268
+ border-radius: 4px;
1269
+ font-size: 0.72rem;
1270
+ cursor: pointer;
1271
+ }
1272
+ .studio-camera-target:hover {
1273
+ background-color: color-mix(in srgb, var(--color-accent) 10%, transparent);
1274
+ }
1275
+ .studio-camera-target.is-on {
1276
+ background-color: color-mix(in srgb, #7c3aed 16%, transparent);
1277
+ }
1278
+ .studio-camera-target-name {
1279
+ flex: 1;
1280
+ overflow: hidden;
1281
+ text-overflow: ellipsis;
1282
+ white-space: nowrap;
1283
+ }
1284
+ .studio-camera-target-id,
1285
+ .studio-camera-target-type {
1286
+ font-family: var(--font-mono);
1287
+ font-size: 0.64rem;
1288
+ color: var(--color-fg-muted);
1289
+ }
1290
+ .studio-camera-kf-list {
1291
+ list-style: none;
1292
+ margin: 0.3rem 0 0;
1293
+ padding: 0;
1294
+ display: flex;
1295
+ flex-wrap: wrap;
1296
+ gap: 0.3rem;
1297
+ }
1298
+ .studio-camera-kf-list li {
1299
+ display: flex;
1300
+ align-items: center;
1301
+ gap: 0.25rem;
1302
+ padding: 0.1rem 0.3rem;
1303
+ border: 1px solid var(--color-border);
1304
+ border-radius: 999px;
1305
+ font-family: var(--font-mono);
1306
+ font-size: 0.68rem;
1307
+ }
1308
+ .studio-camera-kf,
1309
+ .studio-camera-kf-del {
1310
+ border: 0;
1311
+ background: none;
1312
+ color: inherit;
1313
+ font: inherit;
1314
+ cursor: pointer;
1315
+ padding: 0;
1316
+ }
1317
+ .studio-camera-kf-del {
1318
+ color: #ef4444;
1319
+ }
1099
1320
  .studio-tl-total {
1100
1321
  margin-top: 0.3rem;
1101
1322
  padding: 0.2rem 0.4rem;
@@ -1885,6 +2106,81 @@ body.studio-player-popout #clotho-player-preview {
1885
2106
  color: #fff;
1886
2107
  border-color: #b91c1c;
1887
2108
  }
2109
+ /* Camera rows: the same shape as an element row, tinted so a document-level
2110
+ track is not mistaken for one belonging to whatever element is above it. */
2111
+ .studio-tl-camera-row {
2112
+ border-bottom: 1px solid var(--color-border);
2113
+ min-height: 30px;
2114
+ }
2115
+ .studio-tl-camera-row.is-selected .studio-tl-gutter,
2116
+ .studio-tl-camera-row.is-selected .studio-tl-body {
2117
+ background-color: color-mix(
2118
+ in srgb,
2119
+ var(--color-accent) 10%,
2120
+ var(--color-surface-elevated)
2121
+ );
2122
+ }
2123
+ .studio-tl-camera-gutter {
2124
+ gap: 0.35rem;
2125
+ color: #7c3aed;
2126
+ font-weight: 700;
2127
+ cursor: pointer;
2128
+ }
2129
+ .studio-tl-camera-gutter.is-sub {
2130
+ color: var(--color-fg-muted);
2131
+ font-weight: 400;
2132
+ padding-left: 1.4rem;
2133
+ }
2134
+ .studio-tl-camera-add {
2135
+ margin-left: auto;
2136
+ border: 1px solid var(--color-border);
2137
+ border-radius: 4px;
2138
+ background: none;
2139
+ color: inherit;
2140
+ font: inherit;
2141
+ line-height: 1;
2142
+ padding: 0 0.3rem;
2143
+ cursor: pointer;
2144
+ }
2145
+ .studio-tl-camera-track {
2146
+ position: relative;
2147
+ height: 100%;
2148
+ min-height: 30px;
2149
+ }
2150
+ .studio-tl-camera-empty {
2151
+ position: absolute;
2152
+ left: 0.5rem;
2153
+ top: 50%;
2154
+ transform: translateY(-50%);
2155
+ font-size: 0.68rem;
2156
+ color: var(--color-fg-muted);
2157
+ }
2158
+ .studio-tl-camera-focus {
2159
+ position: absolute;
2160
+ top: 5px;
2161
+ bottom: 5px;
2162
+ border-radius: 4px;
2163
+ border: 1px solid #7c3aed;
2164
+ background-color: color-mix(in srgb, #7c3aed 22%, transparent);
2165
+ cursor: grab;
2166
+ overflow: hidden;
2167
+ display: flex;
2168
+ align-items: center;
2169
+ }
2170
+ .studio-tl-camera-focus.is-selected {
2171
+ border-color: #ef4444;
2172
+ background-color: color-mix(in srgb, #ef4444 26%, transparent);
2173
+ }
2174
+ .studio-tl-camera-focus-label {
2175
+ padding: 0 0.35rem;
2176
+ font-size: 0.66rem;
2177
+ font-family: var(--font-mono);
2178
+ white-space: nowrap;
2179
+ pointer-events: none;
2180
+ }
2181
+ .studio-tl-camera-keyframe {
2182
+ color: #7c3aed;
2183
+ }
1888
2184
  .studio-tl-playhead {
1889
2185
  position: absolute;
1890
2186
  top: 0;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnimationDocument, SnapshotMap, Appearance, AnimationElement, TrackKeyframe, Chapter, AnimationEffect, DataValue, LayoutMode, Checkpoint, StoryManifest, StoryEdge, AnimationTemplate, GroupElement, ParameterSchema } from '@kokoa/clotho';
1
+ import { AnimationDocument, SnapshotMap, Appearance, AnimationElement, TrackKeyframe, Chapter, AnimationEffect, CameraFocus, Camera, CameraProperty, DataValue, LayoutMode, Checkpoint, StoryManifest, StoryEdge, AnimationTemplate, GroupElement, ParameterSchema } from '@kokoa/clotho';
2
2
 
3
3
  interface StudioProps {
4
4
  initial: AnimationDocument;
@@ -50,8 +50,11 @@ type Selection = {
50
50
  } | {
51
51
  kind: "effect";
52
52
  effectId: string;
53
+ } | {
54
+ kind: "camera";
55
+ focusIndex?: number;
53
56
  };
54
- type HistoryKind = "meta" | "canvas" | "settings" | "add" | "delete" | "move" | "style" | "rotate" | "resize" | "reorder" | "track" | "appearance" | "chapter" | "effect" | "group" | "layout" | "checkpoint" | "asset" | "other";
57
+ type HistoryKind = "meta" | "canvas" | "settings" | "add" | "delete" | "move" | "style" | "rotate" | "resize" | "reorder" | "track" | "appearance" | "chapter" | "effect" | "camera" | "group" | "layout" | "checkpoint" | "asset" | "other";
55
58
  interface HistoryEntry {
56
59
  snap: string;
57
60
  label: string;
@@ -114,6 +117,53 @@ declare function updateEffect(id: string, patch: Partial<AnimationEffect>): void
114
117
  declare function deleteEffect(id: string): void;
115
118
  declare function updateDuration(ms: number): void;
116
119
 
120
+ declare function getCamera(): Camera;
121
+ /** Whether the document actually carries a camera, as opposed to defaults. */
122
+ declare function hasCamera(): boolean;
123
+ /**
124
+ * Which mechanism decides the view at `time`.
125
+ *
126
+ * The renderer's rule is that a focus entry that has already started wins, and the
127
+ * tracks supply the value otherwise. Direct manipulation on the canvas has to know
128
+ * which one it is editing — dragging the frame when a focus is in control would
129
+ * write track keyframes that the focus immediately overrides, and a control that
130
+ * silently does nothing is worse than one that is disabled.
131
+ */
132
+ type CameraControl = {
133
+ kind: "none";
134
+ } | {
135
+ kind: "tracks";
136
+ } | {
137
+ kind: "focus";
138
+ index: number;
139
+ };
140
+ declare function cameraControlAt(time: number): CameraControl;
141
+ declare function setCameraStrokeScaling(value: Camera["strokeScaling"]): void;
142
+ /** Add or replace the keyframe at `time` on one camera property. */
143
+ declare function setCameraKeyframe(property: CameraProperty, time: number, value: number, ease?: CameraKeyframeEase): void;
144
+ type CameraKeyframeEase = NonNullable<Camera["tracks"][number]["keyframes"][number]["ease"]>;
145
+ /**
146
+ * Move a keyframe in time, keeping its value.
147
+ *
148
+ * Separate from `setCameraKeyframe` because a timeline drag has to remove the old
149
+ * time as well; doing it as remove-then-set would push two history entries for one
150
+ * gesture.
151
+ */
152
+ declare function moveCameraKeyframe(property: CameraProperty, from: number, to: number): void;
153
+ declare function removeCameraKeyframe(property: CameraProperty, time: number): void;
154
+ declare function removeCameraTrack(property: CameraProperty): void;
155
+ declare function addCameraFocus(focus: CameraFocus): void;
156
+ /**
157
+ * Patch one focus entry.
158
+ *
159
+ * Sorting can move it, so the selection is re-pointed at the same entry rather than
160
+ * at whatever ends up at the old index.
161
+ */
162
+ declare function updateCameraFocus(index: number, patch: Partial<CameraFocus>): void;
163
+ declare function deleteCameraFocus(index: number): void;
164
+ /** Remove the camera entirely, so the document renders as it did without one. */
165
+ declare function clearCamera(): void;
166
+
117
167
  declare function updateMeta(patch: Partial<Pick<AnimationDocument, "title" | "description">>): void;
118
168
  /** Update the locales offered to localized text elements in this document. */
119
169
  declare function updateLocales(locales: string[]): void;
@@ -304,4 +354,4 @@ declare function createPerformanceProfilerPlugin(): EditorPluginDefinition;
304
354
 
305
355
  declare function createLinterPlugin(): EditorPluginDefinition;
306
356
 
307
- export { type AnimationRepository, type AnimationSummary, type DocumentImporter, type EditorPluginContext, type EditorPluginDefinition, type EditorPluginHostState, type EditorPluginManifestLike, type EditorPluginMountResult, type EditorPluginPermissionResolver, type EditorPluginPermissions, type EditorPluginSlot, type EditorPluginView, type HistoryEntry, type HistoryKind, type HostOptions, type ImageUploadResolver, type LocalStorageRepositoryOptions, RESPONSIVE_VIEWPORTS, type SceneProfile, type Selection, type StoryEditorOptions, Studio, StudioMount, type StudioMountProps, type StudioProps, addAppearance, addChapter, addCheckpoint, addDefaultResponsiveVariants, addEffect, addElement, animationDocumentFileName, animationDocumentToJson, apiBaseUrl, appendStoryEdge, beginTransient, canRedo, canUndo, childIdsOf, configureAnimationRepository, configureApi, configureHost, createEditorPluginContext, createLayout, createLinterPlugin, createLocalStorageRepository, createPerformanceProfilerPlugin, createResponsiveInspectorPlugin, createStoryEditorPlugin, createTemplateEditorPlugin, createVisualRegressionPlugin, deleteChapter, deleteCheckpoint, deleteEffect, deleteElement, detachFromLayout, downloadAnimationJson, endTransient, findLayoutCollisions, garbageCollectAnimationAssets, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupElements, initialParameterValue, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, layoutIdsFor, markClean, mountEditorPlugins, moveElementToEnd, moveElementToFront, parameterInputValue, placeholderImageUrl, profileAnimation, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeTrack, removeTrackKeyframe, reorderElement, replaceStoryNodeDocument, resetHistory, setCurrentTime, setDef, setDraft, setElementValueAtTime, setSelection, setTrackKeyframe, subscribe, toggleSelectionFor, undo, ungroupElement, uniqueChapterId, uniqueCheckpointId, uniqueEffectId, uniqueElementId, updateAppearance, updateCanvas, updateChapter, updateCheckpoint, updateData, updateDuration, updateEffect, updateElementBase, updateLocales, updateMeta, updateResponsive, updateSettings, validateEditorPlugin };
357
+ export { type AnimationRepository, type AnimationSummary, type CameraControl, type DocumentImporter, type EditorPluginContext, type EditorPluginDefinition, type EditorPluginHostState, type EditorPluginManifestLike, type EditorPluginMountResult, type EditorPluginPermissionResolver, type EditorPluginPermissions, type EditorPluginSlot, type EditorPluginView, type HistoryEntry, type HistoryKind, type HostOptions, type ImageUploadResolver, type LocalStorageRepositoryOptions, RESPONSIVE_VIEWPORTS, type SceneProfile, type Selection, type StoryEditorOptions, Studio, StudioMount, type StudioMountProps, type StudioProps, addAppearance, addCameraFocus, addChapter, addCheckpoint, addDefaultResponsiveVariants, addEffect, addElement, animationDocumentFileName, animationDocumentToJson, apiBaseUrl, appendStoryEdge, beginTransient, cameraControlAt, canRedo, canUndo, childIdsOf, clearCamera, configureAnimationRepository, configureApi, configureHost, createEditorPluginContext, createLayout, createLinterPlugin, createLocalStorageRepository, createPerformanceProfilerPlugin, createResponsiveInspectorPlugin, createStoryEditorPlugin, createTemplateEditorPlugin, createVisualRegressionPlugin, deleteCameraFocus, deleteChapter, deleteCheckpoint, deleteEffect, deleteElement, detachFromLayout, downloadAnimationJson, endTransient, findLayoutCollisions, garbageCollectAnimationAssets, getCamera, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupElements, hasCamera, initialParameterValue, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, layoutIdsFor, markClean, mountEditorPlugins, moveCameraKeyframe, moveElementToEnd, moveElementToFront, parameterInputValue, placeholderImageUrl, profileAnimation, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeCameraKeyframe, removeCameraTrack, removeTrack, removeTrackKeyframe, reorderElement, replaceStoryNodeDocument, resetHistory, setCameraKeyframe, setCameraStrokeScaling, setCurrentTime, setDef, setDraft, setElementValueAtTime, setSelection, setTrackKeyframe, subscribe, toggleSelectionFor, undo, ungroupElement, uniqueChapterId, uniqueCheckpointId, uniqueEffectId, uniqueElementId, updateAppearance, updateCameraFocus, updateCanvas, updateChapter, updateCheckpoint, updateData, updateDuration, updateEffect, updateElementBase, updateLocales, updateMeta, updateResponsive, updateSettings, validateEditorPlugin };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { downloadAnimationJson, configureAnimationRepository } from './chunk-FAI2G3XK.js';
2
- export { addAppearance, addChapter, addCheckpoint, addEffect, addElement, animationDocumentFileName, animationDocumentToJson, apiBaseUrl, beginTransient, canRedo, canUndo, childIdsOf, configureAnimationRepository, configureApi, configureHost, createEditorPluginContext, createLayout, deleteChapter, deleteCheckpoint, deleteEffect, deleteElement, detachFromLayout, downloadAnimationJson, endTransient, findLayoutCollisions, garbageCollectAnimationAssets, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupElements, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, layoutIdsFor, markClean, mountEditorPlugins, moveElementToEnd, moveElementToFront, placeholderImageUrl, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeTrack, removeTrackKeyframe, reorderElement, resetHistory, setCurrentTime, setDef, setDraft, setElementValueAtTime, setSelection, setTrackKeyframe, subscribe, toggleSelectionFor, undo, ungroupElement, uniqueChapterId, uniqueCheckpointId, uniqueEffectId, uniqueElementId, updateAppearance, updateCanvas, updateChapter, updateCheckpoint, updateData, updateDuration, updateEffect, updateElementBase, updateLocales, updateMeta, updateResponsive, updateSettings, validateEditorPlugin } from './chunk-FAI2G3XK.js';
1
+ import { downloadAnimationJson, configureAnimationRepository } from './chunk-N47HF4VB.js';
2
+ export { addAppearance, addCameraFocus, addChapter, addCheckpoint, addEffect, addElement, animationDocumentFileName, animationDocumentToJson, apiBaseUrl, beginTransient, cameraControlAt, canRedo, canUndo, childIdsOf, clearCamera, configureAnimationRepository, configureApi, configureHost, createEditorPluginContext, createLayout, deleteCameraFocus, deleteChapter, deleteCheckpoint, deleteEffect, deleteElement, detachFromLayout, downloadAnimationJson, endTransient, findLayoutCollisions, garbageCollectAnimationAssets, getCamera, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupElements, hasCamera, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, layoutIdsFor, markClean, mountEditorPlugins, moveCameraKeyframe, moveElementToEnd, moveElementToFront, placeholderImageUrl, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeCameraKeyframe, removeCameraTrack, removeTrack, removeTrackKeyframe, reorderElement, resetHistory, setCameraKeyframe, setCameraStrokeScaling, setCurrentTime, setDef, setDraft, setElementValueAtTime, setSelection, setTrackKeyframe, subscribe, toggleSelectionFor, undo, ungroupElement, uniqueChapterId, uniqueCheckpointId, uniqueEffectId, uniqueElementId, updateAppearance, updateCameraFocus, updateCanvas, updateChapter, updateCheckpoint, updateData, updateDuration, updateEffect, updateElementBase, updateLocales, updateMeta, updateResponsive, updateSettings, validateEditorPlugin } from './chunk-N47HF4VB.js';
3
3
  import { useState, useEffect, useRef, useMemo } from 'react';
4
4
  import { usePlayer, AnimationStage } from '@kokoa/clotho/react';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -491,6 +491,7 @@ var SKELETON = `
491
491
  <button type="button" id="studio-undo" class="studio-btn studio-btn-icon" aria-label="\uC2E4\uD589 \uCDE8\uC18C" title="\uC2E4\uD589 \uCDE8\uC18C (\u2318Z)" disabled>\u21B6</button>
492
492
  <button type="button" id="studio-redo" class="studio-btn studio-btn-icon" aria-label="\uB2E4\uC2DC \uC2E4\uD589" title="\uB2E4\uC2DC \uC2E4\uD589 (\u2318\u21E7Z / \u2318Y)" disabled>\u21B7</button>
493
493
  <button type="button" id="studio-grid-toggle" class="studio-btn studio-btn-grid" aria-label="\uACA9\uC790 + \uC2A4\uB0C5" title="\uACA9\uC790 + \uC2A4\uB0C5 (G)" aria-pressed="false"><span class="studio-btn-grid-icon">\u229E</span><span class="studio-btn-grid-label" id="studio-grid-label">\uACA9\uC790 \uB054</span></button>
494
+ <button type="button" id="studio-camera-frame-toggle" class="studio-btn studio-btn-grid" aria-label="\uCE74\uBA54\uB77C \uC601\uC5ED \uD45C\uC2DC" title="\uCE94\uBC84\uC2A4\uC5D0 \uCE74\uBA54\uB77C\uAC00 \uBCF4\uC5EC\uC8FC\uB294 \uC601\uC5ED\uC744 \uD45C\uC2DC\uD569\uB2C8\uB2E4" aria-pressed="true"><span class="studio-btn-grid-icon">\u{1F3A5}</span><span class="studio-btn-grid-label" id="studio-camera-frame-label">\uCE74\uBA54\uB77C \uC601\uC5ED</span></button>
494
495
  <input type="text" id="studio-title" class="studio-title-input" placeholder="\uC81C\uBAA9" aria-label="\uC560\uB2C8\uBA54\uC774\uC158 \uC81C\uBAA9" disabled />
495
496
  <span id="studio-id-display" class="studio-id-display"></span>
496
497
  <span id="studio-status" class="studio-status" aria-live="polite">\uB300\uAE30 \uC911</span>
@@ -702,7 +703,7 @@ function StudioMount({
702
703
  if (repository) configureAnimationRepository(repository);
703
704
  let disposed = false;
704
705
  let cleanup;
705
- void import('./main-SKM3P4X7.js').then(({ initStudio }) => {
706
+ void import('./main-DC6D6RNQ.js').then(({ initStudio }) => {
706
707
  if (disposed) return;
707
708
  cleanup = initStudio({
708
709
  initialId,