@kokoa/clotho-editor 0.1.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.
@@ -0,0 +1,138 @@
1
+ import { AnimationDocument, SnapshotMap, Appearance, AnimationElement, TrackKeyframe, Chapter, AnimationEffect, GroupElement } from '@kokoa/clotho';
2
+
3
+ interface StudioProps {
4
+ initial: AnimationDocument;
5
+ onSave: (def: AnimationDocument) => void | Promise<void>;
6
+ }
7
+ declare function Studio({ initial, onSave }: StudioProps): React.JSX.Element;
8
+
9
+ interface StudioMountProps {
10
+ initialId?: string;
11
+ }
12
+ declare function StudioMount({ initialId, }: StudioMountProps): React.JSX.Element;
13
+
14
+ type Selection = {
15
+ kind: "none";
16
+ } | {
17
+ kind: "element";
18
+ elementId: string;
19
+ } | {
20
+ kind: "elements";
21
+ elementIds: string[];
22
+ } | {
23
+ kind: "chapter";
24
+ chapterId: string;
25
+ } | {
26
+ kind: "effect";
27
+ effectId: string;
28
+ };
29
+ type HistoryKind = "meta" | "canvas" | "settings" | "add" | "delete" | "move" | "style" | "rotate" | "resize" | "reorder" | "track" | "appearance" | "chapter" | "effect" | "group" | "asset" | "other";
30
+ interface HistoryEntry {
31
+ snap: string;
32
+ label: string;
33
+ kind: HistoryKind;
34
+ timestamp: number;
35
+ }
36
+ type Listener = () => void;
37
+
38
+ declare function subscribe(fn: Listener): () => void;
39
+
40
+ declare function isDraft(): boolean;
41
+ declare function setDraft(def: AnimationDocument): void;
42
+ declare function promoteDraftToSaved(): void;
43
+ declare function getDef(): AnimationDocument | null;
44
+ declare function getSelection(): Selection;
45
+ declare function isDirty(): boolean;
46
+ declare function getCurrentTime(): number;
47
+ declare function setCurrentTime(time: number): void;
48
+ declare function setDef(def: AnimationDocument | null, markDirty?: boolean): void;
49
+ declare function markClean(): void;
50
+ declare function setSelection(sel: Selection): void;
51
+ declare function getSelectedElementIds(sel: Selection): string[];
52
+ declare function isElementSelected(sel: Selection, id: string): boolean;
53
+ declare function toggleSelectionFor(sel: Selection, id: string): Selection;
54
+ declare function getCurrentSnapshot(): SnapshotMap;
55
+
56
+ declare function beginTransient(label?: string, kind?: HistoryKind): void;
57
+ declare function endTransient(): void;
58
+ declare function canUndo(): boolean;
59
+ declare function canRedo(): boolean;
60
+ declare function undo(): void;
61
+ declare function redo(): void;
62
+ declare function resetHistory(): void;
63
+ declare function getHistory(): {
64
+ past: readonly HistoryEntry[];
65
+ future: readonly HistoryEntry[];
66
+ };
67
+ declare function jumpBack(steps: number): void;
68
+ declare function jumpForward(steps: number): void;
69
+
70
+ declare function addElement(el: AnimationElement): void;
71
+ declare function deleteElement(id: string): void;
72
+ declare function updateElementBase(id: string, patch: Record<string, unknown>): void;
73
+ declare function reorderElement(sourceId: string, targetId: string, position: "before" | "after"): void;
74
+ declare function moveElementToEnd(id: string): void;
75
+ declare function moveElementToFront(id: string): void;
76
+ declare function addAppearance(id: string, ap: Appearance): void;
77
+ declare function updateAppearance(id: string, apIdx: number, patch: Partial<Appearance>): void;
78
+ declare function removeAppearance(id: string, apIdx: number): void;
79
+ declare function setTrackKeyframe(elementId: string, property: string, time: number, value: TrackKeyframe["value"]): void;
80
+ declare function removeTrackKeyframe(elementId: string, property: string, time: number): void;
81
+ declare function setElementValueAtTime(elementId: string, patch: Record<string, unknown>): void;
82
+ declare function removeTrack(elementId: string, property: string): void;
83
+
84
+ declare function addChapter(c: Chapter): void;
85
+ declare function updateChapter(id: string, patch: Partial<Chapter>): void;
86
+ declare function deleteChapter(id: string): void;
87
+ declare function addEffect(eff: AnimationEffect): void;
88
+ declare function updateEffect(id: string, patch: Partial<AnimationEffect>): void;
89
+ declare function deleteEffect(id: string): void;
90
+ declare function updateDuration(ms: number): void;
91
+
92
+ declare function updateMeta(patch: Partial<Pick<AnimationDocument, "title" | "description">>): void;
93
+ declare function updateCanvas(patch: Partial<AnimationDocument["canvas"]>): void;
94
+ declare function updateSettings(patch: Partial<AnimationDocument["settings"]>): void;
95
+ declare function uniqueElementId(type: string): string;
96
+ declare function uniqueChapterId(): string;
97
+ declare function uniqueEffectId(): string;
98
+ /**
99
+ * Register an external image URL as a document asset and return its id.
100
+ *
101
+ * clotho v1 keeps image sources in a document-level `assets` registry rather than on the
102
+ * element, so a document can be self-contained or host-resolved rather than tied to one
103
+ * site's paths. An identical URL reuses its entry, so dropping the same image twice does
104
+ * not grow the document.
105
+ */
106
+ declare function registerExternalAsset(url: string): string;
107
+ /** Register raw image bytes as an inline (base64) asset and return its id. */
108
+ declare function registerInlineAsset(bytes: Uint8Array, mime: string): string;
109
+ /**
110
+ * Register a `data:` URI as an inline asset and return its id.
111
+ *
112
+ * What a dropped or pasted file becomes. Storing it inline keeps the document
113
+ * self-contained; a non-image or non-base64 URI falls back to an external reference so
114
+ * the element still points at something.
115
+ */
116
+ declare function registerDataUriAsset(dataUri: string): string;
117
+
118
+ declare function isGroup(el: AnimationElement): el is GroupElement;
119
+ /** Direct children of a group, in document order (which is also paint order). */
120
+ declare function childIdsOf(groupId: string): string[];
121
+ declare function groupElements(ids: string[]): string | null;
122
+ declare function ungroupElement(groupId: string): string[];
123
+
124
+ /** Point the editor at the host's animation endpoints. Call once at startup. */
125
+ declare function configureApi(options: {
126
+ baseUrl?: string;
127
+ }): void;
128
+ /** The base currently in use, for hosts that need to build matching links. */
129
+ declare function apiBaseUrl(): string;
130
+
131
+ interface HostOptions {
132
+ /** Image used when inserting a placeholder image element. */
133
+ readonly placeholderImageUrl?: string;
134
+ }
135
+ declare function configureHost(options: HostOptions): void;
136
+ declare function placeholderImageUrl(): string;
137
+
138
+ export { type HistoryEntry, type HistoryKind, type HostOptions, type Selection, Studio, StudioMount, type StudioMountProps, type StudioProps, addAppearance, addChapter, addEffect, addElement, apiBaseUrl, beginTransient, canRedo, canUndo, childIdsOf, configureApi, configureHost, deleteChapter, deleteEffect, deleteElement, endTransient, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupElements, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, markClean, moveElementToEnd, moveElementToFront, placeholderImageUrl, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeTrack, removeTrackKeyframe, reorderElement, resetHistory, setCurrentTime, setDef, setDraft, setElementValueAtTime, setSelection, setTrackKeyframe, subscribe, toggleSelectionFor, undo, ungroupElement, uniqueChapterId, uniqueEffectId, uniqueElementId, updateAppearance, updateCanvas, updateChapter, updateDuration, updateEffect, updateElementBase, updateMeta, updateSettings };
package/dist/index.js ADDED
@@ -0,0 +1,425 @@
1
+ export { addAppearance, addChapter, addEffect, addElement, apiBaseUrl, beginTransient, canRedo, canUndo, childIdsOf, configureApi, configureHost, deleteChapter, deleteEffect, deleteElement, endTransient, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupElements, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, markClean, moveElementToEnd, moveElementToFront, placeholderImageUrl, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeTrack, removeTrackKeyframe, reorderElement, resetHistory, setCurrentTime, setDef, setDraft, setElementValueAtTime, setSelection, setTrackKeyframe, subscribe, toggleSelectionFor, undo, ungroupElement, uniqueChapterId, uniqueEffectId, uniqueElementId, updateAppearance, updateCanvas, updateChapter, updateDuration, updateEffect, updateElementBase, updateMeta, updateSettings } from './chunk-UE32Q6U5.js';
2
+ import { useState, useEffect, useRef } from 'react';
3
+ import { usePlayer, AnimationStage } from '@kokoa/clotho/react';
4
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
+
6
+ function elementsOf(def) {
7
+ return def.elements;
8
+ }
9
+ function Studio({ initial, onSave }) {
10
+ const [def, setDef2] = useState(initial);
11
+ const [selectedId, setSelectedId] = useState(
12
+ elementsOf(initial)[0]?.id ?? null
13
+ );
14
+ const [playing, setPlaying] = useState(false);
15
+ const [time, setTime] = useState(0);
16
+ const [saving, setSaving] = useState(false);
17
+ const [saved, setSaved] = useState(false);
18
+ const { player, state: playerState } = usePlayer(def, { autoplay: false });
19
+ useEffect(() => {
20
+ if (playing) player.play();
21
+ else {
22
+ player.pause();
23
+ player.seek(time);
24
+ }
25
+ }, [playing, player, time]);
26
+ const elements = elementsOf(def);
27
+ const selected = elements.find((e) => e.id === selectedId) ?? null;
28
+ const scalarProps = selected ? Object.entries(selected).filter(
29
+ ([key, value]) => (typeof value === "string" || typeof value === "number") && key !== "id" && key !== "type"
30
+ ) : [];
31
+ function setElements(next) {
32
+ setDef2((current) => ({
33
+ ...current,
34
+ elements: next
35
+ }));
36
+ setSaved(false);
37
+ }
38
+ function updateElement(id, key, raw, numeric) {
39
+ setElements(
40
+ elements.map(
41
+ (e) => e.id === id ? { ...e, [key]: numeric ? Number(raw) : raw } : e
42
+ )
43
+ );
44
+ }
45
+ function addElement2() {
46
+ const id = `el-${Date.now().toString(36)}`;
47
+ const el = {
48
+ type: "text",
49
+ id,
50
+ x: 100,
51
+ y: 100,
52
+ rotation: 0,
53
+ content: "New",
54
+ fontSize: 24,
55
+ color: "#4f46e5",
56
+ textAnchor: "start",
57
+ appearances: [{ start: 0, end: def.duration }],
58
+ tracks: []
59
+ };
60
+ setElements([...elements, el]);
61
+ setSelectedId(id);
62
+ }
63
+ function deleteElement2(id) {
64
+ setElements(elements.filter((e) => e.id !== id));
65
+ if (selectedId === id) setSelectedId(null);
66
+ }
67
+ async function handleSave() {
68
+ setSaving(true);
69
+ try {
70
+ await onSave(def);
71
+ setSaved(true);
72
+ } finally {
73
+ setSaving(false);
74
+ }
75
+ }
76
+ return /* @__PURE__ */ jsxs("div", { className: "studio", children: [
77
+ /* @__PURE__ */ jsxs("aside", { className: "studio-panel studio-left", children: [
78
+ /* @__PURE__ */ jsxs("div", { className: "studio-panel-head", children: [
79
+ /* @__PURE__ */ jsxs("span", { children: [
80
+ "\uC694\uC18C (",
81
+ elements.length,
82
+ ")"
83
+ ] }),
84
+ /* @__PURE__ */ jsx("button", { type: "button", className: "studio-btn", onClick: addElement2, children: "+ \uCD94\uAC00" })
85
+ ] }),
86
+ /* @__PURE__ */ jsx("ul", { className: "studio-el-list", children: elements.map((e) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
87
+ "button",
88
+ {
89
+ type: "button",
90
+ className: e.id === selectedId ? "studio-el active" : "studio-el",
91
+ onClick: () => setSelectedId(e.id),
92
+ children: [
93
+ /* @__PURE__ */ jsx("span", { className: "studio-el-type", children: e.type }),
94
+ /* @__PURE__ */ jsx("span", { className: "studio-el-id", children: e.id })
95
+ ]
96
+ }
97
+ ) }, e.id)) })
98
+ ] }),
99
+ /* @__PURE__ */ jsxs("div", { className: "studio-preview", children: [
100
+ /* @__PURE__ */ jsxs("div", { className: "studio-preview-bar", children: [
101
+ /* @__PURE__ */ jsx(
102
+ "button",
103
+ {
104
+ type: "button",
105
+ className: "studio-btn",
106
+ onClick: () => setPlaying((p) => !p),
107
+ children: playing ? "\u23F8 \uC815\uC9C0" : "\u25B6 \uC7AC\uC0DD"
108
+ }
109
+ ),
110
+ /* @__PURE__ */ jsx(
111
+ "input",
112
+ {
113
+ type: "range",
114
+ min: 0,
115
+ max: def.duration,
116
+ step: 50,
117
+ value: time,
118
+ disabled: playing,
119
+ onChange: (e) => {
120
+ setTime(Number(e.target.value));
121
+ setPlaying(false);
122
+ }
123
+ }
124
+ ),
125
+ /* @__PURE__ */ jsxs("span", { className: "studio-time", children: [
126
+ time,
127
+ " / ",
128
+ def.duration,
129
+ "ms"
130
+ ] }),
131
+ /* @__PURE__ */ jsx(
132
+ "button",
133
+ {
134
+ type: "button",
135
+ className: "studio-btn studio-save",
136
+ onClick: () => void handleSave(),
137
+ disabled: saving,
138
+ children: saving ? "\uC800\uC7A5 \uC911\u2026" : saved ? "\u2713 \uC800\uC7A5\uB428" : "\uC800\uC7A5"
139
+ }
140
+ )
141
+ ] }),
142
+ /* @__PURE__ */ jsx("div", { className: "studio-canvas", children: /* @__PURE__ */ jsx(AnimationStage, { doc: def, time: playing ? playerState.time : time }) })
143
+ ] }),
144
+ /* @__PURE__ */ jsxs("aside", { className: "studio-panel studio-right", children: [
145
+ /* @__PURE__ */ jsx("div", { className: "studio-panel-head", children: "\uBA54\uD0C0" }),
146
+ /* @__PURE__ */ jsxs("label", { className: "studio-field", children: [
147
+ /* @__PURE__ */ jsx("span", { children: "\uC81C\uBAA9" }),
148
+ /* @__PURE__ */ jsx(
149
+ "input",
150
+ {
151
+ value: def.title,
152
+ onChange: (e) => {
153
+ setDef2((d) => ({ ...d, title: e.target.value }));
154
+ setSaved(false);
155
+ }
156
+ }
157
+ )
158
+ ] }),
159
+ /* @__PURE__ */ jsxs("label", { className: "studio-field", children: [
160
+ /* @__PURE__ */ jsx("span", { children: "duration (ms)" }),
161
+ /* @__PURE__ */ jsx(
162
+ "input",
163
+ {
164
+ type: "number",
165
+ value: def.duration,
166
+ onChange: (e) => {
167
+ setDef2((d) => ({ ...d, duration: Number(e.target.value) }));
168
+ setSaved(false);
169
+ }
170
+ }
171
+ )
172
+ ] }),
173
+ /* @__PURE__ */ jsxs("div", { className: "studio-panel-head", children: [
174
+ "\uC18D\uC131 ",
175
+ selected ? `(${selected.type})` : ""
176
+ ] }),
177
+ selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
178
+ scalarProps.map(([key, value]) => /* @__PURE__ */ jsxs("label", { className: "studio-field", children: [
179
+ /* @__PURE__ */ jsx("span", { children: key }),
180
+ /* @__PURE__ */ jsx(
181
+ "input",
182
+ {
183
+ value: String(value),
184
+ onChange: (e) => updateElement(
185
+ selected.id,
186
+ key,
187
+ e.target.value,
188
+ typeof value === "number"
189
+ )
190
+ }
191
+ )
192
+ ] }, key)),
193
+ /* @__PURE__ */ jsx(
194
+ "button",
195
+ {
196
+ type: "button",
197
+ className: "studio-btn studio-del",
198
+ onClick: () => deleteElement2(selected.id),
199
+ children: "\uC694\uC18C \uC0AD\uC81C"
200
+ }
201
+ )
202
+ ] }) : /* @__PURE__ */ jsx("p", { className: "studio-empty", children: "\uC694\uC18C\uB97C \uC120\uD0DD\uD558\uC138\uC694." })
203
+ ] })
204
+ ] });
205
+ }
206
+ var CANVAS_PRESETS = [
207
+ { id: "800x500", label: "8:5", sub: "800\xD7500", w: 32, h: 20 },
208
+ { id: "1280x720", label: "16:9", sub: "HD", w: 32, h: 18 },
209
+ { id: "1024x768", label: "4:3", sub: "1024\xD7768", w: 28, h: 21 },
210
+ { id: "600x600", label: "1:1", sub: "\uC815\uC0AC\uAC01", w: 22, h: 22 },
211
+ { id: "1200x400", label: "3:1", sub: "\uC640\uC774\uB4DC", w: 36, h: 12 },
212
+ { id: "375x812", label: "9:19", sub: "\uBAA8\uBC14\uC77C", w: 14, h: 30 }
213
+ ];
214
+ var PRESET_HTML = CANVAS_PRESETS.map(
215
+ (p) => `
216
+ <button type="button" class="studio-preset-btn" aria-label="\uCE94\uBC84\uC2A4 \uD06C\uAE30 ${p.id} (${p.sub})" title="${p.id} \xB7 ${p.sub}" data-canvas-preset="${p.id}">
217
+ <svg class="studio-preset-thumb" viewBox="0 0 ${p.w + 2} ${p.h + 2}" width="${p.w + 2}" height="${p.h + 2}" aria-hidden="true">
218
+ <rect x="1" y="1" width="${p.w}" height="${p.h}" rx="2" />
219
+ </svg>
220
+ <span class="studio-preset-label">${p.label}</span>
221
+ <span class="studio-preset-sub">${p.sub}</span>
222
+ </button>`
223
+ ).join("");
224
+ var SKELETON = `
225
+ <div id="studio-app" class="studio-app">
226
+ <header class="studio-header">
227
+ <h1 class="studio-header-title">\u{1F3AC} \uC560\uB2C8\uBA54\uC774\uC158 \uC2A4\uD29C\uB514\uC624</h1>
228
+ <div class="studio-header-actions">
229
+ <button type="button" id="studio-open" class="studio-btn" aria-label="\uC800\uC7A5\uB41C \uC560\uB2C8\uBA54\uC774\uC158 \uC5F4\uAE30">\u{1F4C1} \uC5F4\uAE30</button>
230
+ <button type="button" id="studio-new" class="studio-btn" aria-label="\uC0C8 \uC560\uB2C8\uBA54\uC774\uC158 \uB9CC\uB4E4\uAE30">\uFF0B \uC0C8 \uC560\uB2C8\uBA54\uC774\uC158</button>
231
+ <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>
232
+ <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>
233
+ <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>
234
+ <input type="text" id="studio-title" class="studio-title-input" placeholder="\uC81C\uBAA9" aria-label="\uC560\uB2C8\uBA54\uC774\uC158 \uC81C\uBAA9" disabled />
235
+ <span id="studio-id-display" class="studio-id-display"></span>
236
+ <span id="studio-status" class="studio-status" aria-live="polite">\uB300\uAE30 \uC911</span>
237
+ <button type="button" id="studio-save" class="studio-btn studio-btn-primary" aria-label="\uC800\uC7A5" disabled>\u{1F4BE} \uC800\uC7A5 (\u2318S)</button>
238
+ <button type="button" id="studio-delete" class="studio-btn studio-btn-danger" aria-label="\uC0AD\uC81C" disabled>\u{1F5D1} \uC0AD\uC81C</button>
239
+ </div>
240
+ </header>
241
+
242
+ <div class="studio-body">
243
+ <aside class="studio-tools" aria-label="\uC694\uC18C \uCD94\uAC00">
244
+ <div class="studio-tools-section">
245
+ <div class="studio-tools-title">\uC694\uC18C \uCD94\uAC00</div>
246
+ <button type="button" class="studio-tool-btn" data-add-element="rect" title="\uC0AC\uAC01\uD615" aria-label="\uC0AC\uAC01\uD615 \uCD94\uAC00">\u25A1 Rect</button>
247
+ <button type="button" class="studio-tool-btn" data-add-element="circle" title="\uC6D0" aria-label="\uC6D0 \uCD94\uAC00">\u25CB Circle</button>
248
+ <button type="button" class="studio-tool-btn" data-add-element="line" title="\uC120" aria-label="\uC120 \uCD94\uAC00">\uFF0F Line</button>
249
+ <button type="button" class="studio-tool-btn" data-add-element="arrow" title="\uD654\uC0B4\uD45C" aria-label="\uD654\uC0B4\uD45C \uCD94\uAC00">\u2197 Arrow</button>
250
+ <button type="button" class="studio-tool-btn" data-add-element="text" title="\uD14D\uC2A4\uD2B8" aria-label="\uD14D\uC2A4\uD2B8 \uCD94\uAC00">T Text</button>
251
+ <button type="button" class="studio-tool-btn" data-add-element="image" title="\uC774\uBBF8\uC9C0" aria-label="\uC774\uBBF8\uC9C0 \uCD94\uAC00">\u{1F5BC} Image</button>
252
+ <button type="button" class="studio-tool-btn" data-add-element="path" title="SVG path" aria-label="SVG path \uCD94\uAC00">\u270E Path</button>
253
+ <button type="button" class="studio-tool-btn" data-add-element="polygon" title="\uB2E4\uAC01\uD615" aria-label="\uB2E4\uAC01\uD615 \uCD94\uAC00">\u2B22 Polygon</button>
254
+ <button type="button" class="studio-tool-btn" id="studio-open-icons" title="\uC544\uC774\uCF58 \uB77C\uC774\uBE0C\uB7EC\uB9AC" aria-label="\uC544\uC774\uCF58 \uB77C\uC774\uBE0C\uB7EC\uB9AC \uCD94\uAC00">\u{1F3A8} Icons</button>
255
+ </div>
256
+ <div class="studio-tools-section">
257
+ <div class="studio-tools-title">\uCE94\uBC84\uC2A4 \uD06C\uAE30</div>
258
+ <div class="studio-canvas-size-row">
259
+ <input type="number" id="studio-canvas-width" class="studio-canvas-size-input" min="100" max="8000" step="10" placeholder="width" />
260
+ <span class="studio-canvas-size-x">\xD7</span>
261
+ <input type="number" id="studio-canvas-height" class="studio-canvas-size-input" min="100" max="8000" step="10" placeholder="height" />
262
+ </div>
263
+ <div class="studio-canvas-presets">${PRESET_HTML}</div>
264
+ </div>
265
+ <div class="studio-tools-section">
266
+ <div class="studio-tools-title">\uC774\uBBF8\uC9C0</div>
267
+ <button type="button" class="studio-tool-btn" id="studio-image-upload">\u{1F4E4} \uC774\uBBF8\uC9C0 \uC5C5\uB85C\uB4DC</button>
268
+ <input type="file" id="studio-image-file" accept="image/*" hidden />
269
+ <div class="studio-tools-hint">\uC774\uBBF8\uC9C0\uB97C \uCE94\uBC84\uC2A4\uC5D0 \uC9C1\uC811 \uB4DC\uB798\uADF8\uD558\uAC70\uB098 \u2318V \uB85C \uBD99\uC5EC\uB123\uC5B4\uB3C4 \uB429\uB2C8\uB2E4.</div>
270
+ </div>
271
+ <div class="studio-tools-section">
272
+ <div class="studio-tools-title">\uB2E8\uCD95\uD0A4</div>
273
+ <button type="button" class="studio-tool-btn" id="studio-help">\u2328 \uB2E8\uCD95\uD0A4 \uBCF4\uAE30 (?)</button>
274
+ </div>
275
+ <div class="studio-tools-section">
276
+ <div class="studio-tools-title">\uC694\uC18C \uBAA9\uB85D</div>
277
+ <input type="text" id="studio-element-search" class="studio-element-search" placeholder="\u{1F50D} \uAC80\uC0C9 (name / label / id / type)" autocomplete="off" />
278
+ <ul id="studio-element-list" class="studio-element-list"></ul>
279
+ </div>
280
+ </aside>
281
+
282
+ <main class="studio-canvas-wrap">
283
+ <div class="studio-canvas-frame">
284
+ <svg id="studio-canvas" class="studio-canvas" xmlns="http://www.w3.org/2000/svg"></svg>
285
+ </div>
286
+ <button type="button" id="studio-floating-help" class="studio-floating-help" aria-label="\uB2E8\uCD95\uD0A4 \uBCF4\uAE30" title="\uB2E8\uCD95\uD0A4 \uBCF4\uAE30 (? / Shift+/)">?</button>
287
+ <div id="studio-help-hint" class="studio-help-hint" hidden>
288
+ <span>\u{1F4A1} \uB2E8\uCD95\uD0A4\uB294 <kbd>?</kbd> \uB610\uB294 \uC6B0\uCE21 \uD558\uB2E8 \uBC84\uD2BC\uC73C\uB85C \uD655\uC778\uD558\uC138\uC694</span>
289
+ <button type="button" id="studio-help-hint-close" aria-label="\uB2EB\uAE30">\u2715</button>
290
+ </div>
291
+ </main>
292
+
293
+ <aside class="studio-props" aria-label="\uC18D\uC131 \uD328\uB110">
294
+ <div id="studio-props-content" class="studio-props-content">
295
+ <p class="studio-props-empty">\uC694\uC18C \uB610\uB294 step \uC744 \uC120\uD0DD\uD558\uC138\uC694.</p>
296
+ </div>
297
+ </aside>
298
+ </div>
299
+
300
+ <div id="studio-timeline-resizer" class="studio-timeline-resizer" role="separator" aria-orientation="horizontal" aria-label="\uD0C0\uC784\uB77C\uC778 \uC601\uC5ED \uD06C\uAE30 \uC870\uC815"></div>
301
+ <footer class="studio-timeline-wrap">
302
+ <div class="studio-timeline-header">
303
+ <button type="button" id="studio-play" class="studio-btn">\u25B6 Play</button>
304
+ <button type="button" id="studio-restart" class="studio-btn">\u27F2 Reset</button>
305
+ <label class="studio-speed">\uC18D\uB3C4
306
+ <input type="range" id="studio-speed" min="0.25" max="3" step="0.25" value="1" />
307
+ <span id="studio-speed-value">1.00x</span>
308
+ </label>
309
+ <span class="studio-timeline-spacer"></span>
310
+ <button type="button" id="studio-add-step" class="studio-btn">\uFF0B Chapter \uCD94\uAC00</button>
311
+ </div>
312
+ <div class="studio-timeline-tracks-wrap">
313
+ <div id="studio-timeline-tracks" class="studio-timeline-tracks"></div>
314
+ </div>
315
+ <div class="studio-element-tracks-wrap">
316
+ <div id="studio-element-tracks" class="studio-element-tracks"></div>
317
+ </div>
318
+ </footer>
319
+
320
+ <dialog id="studio-library-dialog" class="studio-dialog">
321
+ <div class="studio-dialog-header">
322
+ <h2>\uC800\uC7A5\uB41C \uC560\uB2C8\uBA54\uC774\uC158</h2>
323
+ <button type="button" class="studio-dialog-close" data-studio-dialog-close>\u2715</button>
324
+ </div>
325
+ <div class="studio-dialog-body">
326
+ <ul id="studio-library-list" class="studio-library-list"></ul>
327
+ </div>
328
+ </dialog>
329
+
330
+ <dialog id="studio-icon-dialog" class="studio-dialog">
331
+ <div class="studio-dialog-header">
332
+ <h2>\u{1F3A8} \uC544\uC774\uCF58 \uB77C\uC774\uBE0C\uB7EC\uB9AC</h2>
333
+ <button type="button" class="studio-dialog-close" data-icon-dialog-close>\u2715</button>
334
+ </div>
335
+ <div class="studio-dialog-body">
336
+ <input type="search" id="studio-icon-search" placeholder="\uC544\uC774\uCF58 \uAC80\uC0C9\u2026" class="studio-icon-search-input" />
337
+ <div id="studio-icon-list"></div>
338
+ </div>
339
+ </dialog>
340
+
341
+ <dialog id="studio-help-dialog" class="studio-dialog studio-dialog-small">
342
+ <div class="studio-dialog-header">
343
+ <h2>\u2328 \uB2E8\uCD95\uD0A4</h2>
344
+ <button type="button" class="studio-dialog-close" data-studio-dialog-close>\u2715</button>
345
+ </div>
346
+ <div class="studio-dialog-body">
347
+ <table class="studio-help-table">
348
+ <tbody>
349
+ <tr><th>\u2318 S</th><td>\uC800\uC7A5</td></tr>
350
+ <tr><th>\u2318 Z / \u2318 \u21E7 Z</th><td>Undo / Redo</td></tr>
351
+ <tr><th>\u2318 C / V / X</th><td>\uC694\uC18C \uBCF5\uC0AC / \uBD99\uC5EC\uB123\uAE30 / \uC798\uB77C\uB0B4\uAE30</td></tr>
352
+ <tr><th>Delete / Backspace</th><td>\uC120\uD0DD\uB41C \uC694\uC18C \uB610\uB294 step \uC0AD\uC81C</td></tr>
353
+ <tr><th>?</th><td>\uC774 \uD654\uBA74 \uC5F4\uAE30</td></tr>
354
+ <tr><th>Esc</th><td>\uC120\uD0DD \uD574\uC81C / \uB2E4\uC774\uC5BC\uB85C\uADF8 \uB2EB\uAE30</td></tr>
355
+ <tr><th>\uB4DC\uB798\uADF8 (\uCE94\uBC84\uC2A4)</th><td>\uC694\uC18C \uC774\uB3D9</td></tr>
356
+ <tr><th>\uB4DC\uB798\uADF8 (\uC694\uC18C \uC704 dot)</th><td>\uD654\uC0B4\uD45C \uC5F0\uACB0</td></tr>
357
+ <tr><th>\uB4DC\uB798\uADF8 (\uC120/\uD654\uC0B4\uD45C \uB05D\uC810)</th><td>\uB05D\uC810 \uC7AC\uBC30\uCE58 + \uB2E4\uB978 \uC694\uC18C\uC5D0 sticky</td></tr>
358
+ <tr><th>\uB4DC\uB798\uADF8 (\uD68C\uC804 \uD578\uB4E4)</th><td>\uC694\uC18C \uD68C\uC804</td></tr>
359
+ <tr><th>\uB4DC\uB798\uADF8 (\uD0C0\uC784\uB77C\uC778 \uBC14 \uC6B0\uCE21)</th><td>step duration \uC870\uC815</td></tr>
360
+ <tr><th>\uC774\uBBF8\uC9C0 \uB4DC\uB798\uADF8 in</th><td>\uC774\uBBF8\uC9C0 \uC5C5\uB85C\uB4DC + \uBC30\uCE58</td></tr>
361
+ </tbody>
362
+ </table>
363
+ </div>
364
+ </dialog>
365
+
366
+ <dialog id="studio-new-dialog" class="studio-dialog studio-dialog-small">
367
+ <div class="studio-dialog-header">
368
+ <h2 id="studio-new-dialog-title">\uC0C8 \uC560\uB2C8\uBA54\uC774\uC158 \uB9CC\uB4E4\uAE30</h2>
369
+ <button type="button" class="studio-dialog-close" data-studio-dialog-close aria-label="\uB2EB\uAE30">\u2715</button>
370
+ </div>
371
+ <div class="studio-dialog-body">
372
+ <label class="studio-field"><span>ID (\uC601\uBB38 \uC18C\uBB38\uC790 / \uC22B\uC790 / - / _)</span>
373
+ <input type="text" id="studio-new-id" placeholder="\uC608: user-login-flow" />
374
+ </label>
375
+ <label class="studio-field"><span>\uC81C\uBAA9</span>
376
+ <input type="text" id="studio-new-title" placeholder="\uC0AC\uC6A9\uC790 \uB85C\uADF8\uC778 \uD750\uB984" />
377
+ </label>
378
+ <div id="studio-new-error" class="studio-new-error"></div>
379
+ </div>
380
+ <footer class="studio-dialog-footer">
381
+ <button type="button" class="studio-btn" data-studio-dialog-close>\uCDE8\uC18C</button>
382
+ <button type="button" id="studio-new-create" class="studio-btn studio-btn-primary">\uB9CC\uB4E4\uAE30</button>
383
+ </footer>
384
+ </dialog>
385
+
386
+ <dialog id="studio-palette-dialog" class="studio-palette-dialog">
387
+ <input type="text" id="studio-palette-input" class="studio-palette-input" placeholder="\u{1F50D} \uBA85\uB839 \uB610\uB294 \uC694\uC18C \uAC80\uC0C9\u2026" autocomplete="off" spellcheck="false" />
388
+ <ul id="studio-palette-list" class="studio-palette-list"></ul>
389
+ <div class="studio-palette-footer">\u2191\u2193 \uC774\uB3D9 \xB7 Enter \uC2E4\uD589 \xB7 Esc \uB2EB\uAE30</div>
390
+ </dialog>
391
+
392
+ <dialog id="studio-history-dialog" class="studio-history-dialog">
393
+ <div class="studio-history-header">
394
+ <h2>\u{1F4DC} \uC791\uC5C5 \uC774\uB825</h2>
395
+ <button type="button" class="studio-dialog-close" data-studio-dialog-close aria-label="\uB2EB\uAE30">\u2715</button>
396
+ </div>
397
+ <ul id="studio-history-list" class="studio-history-list"></ul>
398
+ <div class="studio-history-footer">\uD56D\uBAA9\uC744 \uD074\uB9AD\uD558\uBA74 \uD574\uB2F9 \uC2DC\uC810\uC73C\uB85C \uC774\uB3D9\uD569\uB2C8\uB2E4 \xB7 \u2318Z / \u2318\u21E7Z</div>
399
+ </dialog>
400
+ </div>
401
+ `;
402
+ function StudioMount({
403
+ initialId
404
+ }) {
405
+ const inited = useRef(false);
406
+ useEffect(() => {
407
+ if (inited.current) return;
408
+ inited.current = true;
409
+ let disposed = false;
410
+ void import('./main-SWHHYTLM.js').then(({ initStudio }) => {
411
+ if (disposed) return;
412
+ initStudio(initialId ? { initialId } : void 0);
413
+ });
414
+ return () => {
415
+ disposed = true;
416
+ document.body.classList.remove("editor-active");
417
+ document.documentElement.classList.remove("editor-active");
418
+ };
419
+ }, [initialId]);
420
+ return /* @__PURE__ */ jsx("section", { className: "studio-shell w-full", "data-pagefind-ignore": "all", children: /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: SKELETON } }) });
421
+ }
422
+
423
+ export { Studio, StudioMount };
424
+ //# sourceMappingURL=index.js.map
425
+ //# sourceMappingURL=index.js.map