stillmade-sdk 0.1.47 → 0.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/AGENTS.md +6 -2
  2. package/FRAME_VIEWS.md +71 -5
  3. package/INTERFACE_DESIGN.md +65 -2
  4. package/MODULES.md +26 -3
  5. package/PASTE_TO_LLM.md +25 -10
  6. package/README.md +15 -0
  7. package/SDK.md +349 -51
  8. package/blocks/native/sm.board-editor/versions/3.0.0/package.js +921 -0
  9. package/blocks/native/sm.editor/versions/3.0.0/package.js +751 -0
  10. package/blocks/native/sm.panel-strip/versions/3.0.0/package.js +350 -0
  11. package/blocks/native/sm.photo-editor/versions/1.0.0/package.js +6693 -0
  12. package/blocks/native/sm.voiceover/versions/3.0.0/package.js +513 -0
  13. package/docs/build/block-builder-contract.md +2 -2
  14. package/docs/build/custom-interface.md +23 -3
  15. package/docs/build/frame-views.md +71 -5
  16. package/docs/build/hosted-text.md +8 -3
  17. package/docs/build/interface-design.md +65 -2
  18. package/docs/build/media-analysis.md +18 -1
  19. package/docs/build/modules.md +26 -3
  20. package/docs/build/with-your-ai.md +14 -0
  21. package/docs/build/workspaces.md +42 -0
  22. package/docs/reference/authoring-guide.md +4 -4
  23. package/docs/reference/cli.md +8 -3
  24. package/docs/reference/execution.md +13 -5
  25. package/docs/reference/first-party-blocks.md +5 -4
  26. package/docs/reference/generation-models.md +4 -0
  27. package/docs/reference/limits.md +9 -2
  28. package/docs/reference/permissions.md +4 -0
  29. package/docs/reference/view-bridge.md +6 -3
  30. package/docs/reference/view-rules.md +1 -1
  31. package/docs/tools/llm.md +25 -10
  32. package/package.json +1 -1
  33. package/packages/block-cli/cli.js +124 -12
  34. package/packages/block-cli/dev-host/assets/host.js +55 -30
  35. package/packages/block-cli/dev-host/assets/index.css +1 -1
  36. package/packages/block-cli/dev-server.js +74 -23
  37. package/packages/block-cli/format.js +20 -1
  38. package/packages/block-cli/ready-gate.js +34 -2
  39. package/packages/block-cli/templates.js +61 -0
  40. package/packages/block-cli/type-packages.js +7 -4
  41. package/packages/block-cli/workspace-dev.js +124 -0
  42. package/packages/block-cli/workspace-pack.js +62 -0
  43. package/packages/block-cli/workspace.js +304 -0
  44. package/packages/block-platform/block-readiness.js +5 -4
  45. package/packages/block-platform/canvas.js +9 -0
  46. package/packages/block-platform/catalog.js +5 -3
  47. package/packages/block-platform/listings.js +2 -2
  48. package/packages/block-platform/node-defaults.js +2 -2
  49. package/packages/block-platform/plain-descriptions.js +1 -0
  50. package/packages/block-platform/type-workspace.js +8 -0
  51. package/packages/block-sdk/admission.js +2 -2
  52. package/packages/block-sdk/builder-contract.js +3 -3
  53. package/packages/block-sdk/capabilities.js +32 -12
  54. package/packages/block-sdk/contracts.js +11 -2
  55. package/packages/block-sdk/first-party-identities.js +9 -0
  56. package/packages/block-sdk/first-party-packages.js +9 -1
  57. package/packages/block-sdk/folder-package.js +34 -2
  58. package/packages/block-sdk/generation-catalog.js +7 -0
  59. package/packages/block-sdk/index.d.ts +13 -5
  60. package/packages/block-sdk/index.js +1 -1
  61. package/packages/block-sdk/mock-host.js +7 -0
  62. package/packages/block-sdk/module-hosted.js +11 -1
  63. package/packages/block-sdk/module-media-access.js +5 -3
  64. package/packages/block-sdk/permission-summary.js +1 -1
  65. package/packages/block-sdk/portable-media-actions.d.ts +4 -1
  66. package/packages/block-sdk/portable-media-actions.js +16 -1
  67. package/packages/block-sdk/remix-policy.js +2 -2
  68. package/packages/block-sdk/staged-releases.js +5 -0
  69. package/packages/block-sdk/stillmade-pack.js +64 -0
  70. package/packages/block-sdk/testing.js +2 -2
  71. package/packages/block-sdk/view-bridge.js +6 -3
  72. package/packages/block-sdk/view-design.js +59 -12
  73. package/packages/block-sdk/view.js +47 -9
  74. package/packages/design-system/block-kit.css +6 -0
  75. package/packages/design-system/theme.css +2 -0
@@ -0,0 +1,751 @@
1
+ // Generated by frame/build-package.mjs from the original Editor screen. Edit the screen or frame/, then rebuild.
2
+ export default {
3
+ "manifest": {
4
+ "schemaVersion": 1,
5
+ "sdkVersion": "0.1.0",
6
+ "id": "sm.editor",
7
+ "version": "3.0.0",
8
+ "name": "Editor",
9
+ "description": "Cut your video on a timeline: clips, pictures, captions, text, music, sound effects, transitions and versions of the cut, with the narration and pictures earlier Steps made.",
10
+ "kind": "workspace",
11
+ "runtime": "module",
12
+ "license": "MIT",
13
+ "inputs": {
14
+ "editor": {
15
+ "type": "timeline",
16
+ "primary": true,
17
+ "required": false,
18
+ "semantic": "editor_timeline",
19
+ "description": "The Editor document on this screen: every timeline, clip, caption and setting."
20
+ },
21
+ "cut": {
22
+ "type": "timeline",
23
+ "required": false,
24
+ "semantic": "editor_handoff",
25
+ "description": "An Editor cut from an earlier Step (White Board Motion's boards), opened when this Editor has no edit of its own yet."
26
+ },
27
+ "media": {
28
+ "type": "asset[]",
29
+ "required": false,
30
+ "semantic": "source_media",
31
+ "description": "Clips and pictures from earlier Steps, shown in the Editor's media to put on the timeline."
32
+ },
33
+ "narrationAudio": {
34
+ "type": "audio",
35
+ "required": false,
36
+ "semantic": "narration_audio",
37
+ "description": "The narration from an earlier Step, placed on the voiceover track."
38
+ },
39
+ "narrationTiming": {
40
+ "type": "transcript",
41
+ "required": false,
42
+ "semantic": "timed_transcript",
43
+ "description": "The narration's word timings, for captions and cuts."
44
+ },
45
+ "narrationScript": {
46
+ "type": "script",
47
+ "required": false,
48
+ "semantic": "narration_script",
49
+ "description": "The narration script."
50
+ },
51
+ "panelImages": {
52
+ "type": "asset[]",
53
+ "required": false,
54
+ "semantic": "panel_images",
55
+ "description": "The saved pictures, clips and GIFs of the Image Panels, for the timeline and its export."
56
+ },
57
+ "panels": {
58
+ "type": "panel-document",
59
+ "required": false,
60
+ "semantic": "image_panel_document",
61
+ "description": "Image Panels from an earlier Step, laid out on the timeline."
62
+ },
63
+ "make": {
64
+ "type": "text",
65
+ "required": false,
66
+ "semantic": "editor_action",
67
+ "description": "What a run makes: a picture, a clip, word timings, a saved file or the result."
68
+ },
69
+ "request": {
70
+ "type": "json",
71
+ "schema": "json",
72
+ "required": false,
73
+ "semantic": "editor_request",
74
+ "description": "The details of what to make."
75
+ },
76
+ "references": {
77
+ "type": "image[]",
78
+ "required": false,
79
+ "semantic": "reference_image",
80
+ "description": "Saved pictures a new picture keeps."
81
+ },
82
+ "firstFrame": {
83
+ "type": "image",
84
+ "required": false,
85
+ "semantic": "first_frame",
86
+ "description": "The picture a clip starts on."
87
+ },
88
+ "lastFrame": {
89
+ "type": "image",
90
+ "required": false,
91
+ "semantic": "last_frame",
92
+ "description": "The picture a clip ends on."
93
+ },
94
+ "audio": {
95
+ "type": "asset",
96
+ "required": false,
97
+ "semantic": "source_audio",
98
+ "description": "Saved sound to find the words of."
99
+ },
100
+ "files": {
101
+ "type": "asset[]",
102
+ "required": false,
103
+ "semantic": "export_media",
104
+ "description": "The saved clips, pictures and sound an export's timeline uses."
105
+ },
106
+ "dataUrl": {
107
+ "type": "text",
108
+ "required": false,
109
+ "semantic": "file_data",
110
+ "description": "A file the screen saves: a picture, a clip or a sound."
111
+ },
112
+ "key": {
113
+ "type": "text",
114
+ "required": false,
115
+ "semantic": "storage_key",
116
+ "description": "A file name or a saved setting of this Block."
117
+ },
118
+ "value": {
119
+ "type": "json",
120
+ "schema": "json",
121
+ "required": false,
122
+ "semantic": "storage_value",
123
+ "description": "The value of that setting."
124
+ }
125
+ },
126
+ "outputs": {
127
+ "editor": {
128
+ "type": "timeline",
129
+ "primary": true,
130
+ "required": false,
131
+ "role": "editor_timeline",
132
+ "semantic": "editor_timeline",
133
+ "description": "The complete Editor document for the next Step."
134
+ },
135
+ "export": {
136
+ "type": "asset",
137
+ "required": false,
138
+ "semantic": "exported_video",
139
+ "description": "The exported video (or sound), to download or pass on."
140
+ },
141
+ "made": {
142
+ "type": "asset[]",
143
+ "required": false,
144
+ "semantic": "made_media",
145
+ "description": "The pictures, clips or files a run made."
146
+ },
147
+ "result": {
148
+ "type": "json",
149
+ "schema": "json",
150
+ "required": false,
151
+ "semantic": "editor_answer",
152
+ "description": "What the screen asked for: a picture, word timings, a saved file."
153
+ }
154
+ },
155
+ "permissions": {
156
+ "project": [],
157
+ "capabilities": [
158
+ "image.generate",
159
+ "video.generate",
160
+ "audio.transcribe"
161
+ ],
162
+ "actions": [
163
+ "stock.search",
164
+ "final.render"
165
+ ],
166
+ "network": [],
167
+ "filesystem": [],
168
+ "secrets": []
169
+ },
170
+ "platforms": {
171
+ "phone": {
172
+ "supported": true
173
+ },
174
+ "browser": {
175
+ "supported": true
176
+ },
177
+ "desktop": {
178
+ "supported": true
179
+ }
180
+ },
181
+ "provenance": {
182
+ "notice": "MIT License\n\nCopyright (c) <year> <copyright holders>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"
183
+ }
184
+ },
185
+ "module": {
186
+ "schemaVersion": 1,
187
+ "entry": "main.js",
188
+ "files": {
189
+ "main.js": {
190
+ "sha256": "a8bd224898c99235a736b0cf6bcda78718301645ff03fb3c1990ab7ea4cec959",
191
+ "bytes": 5966,
192
+ "mediaType": "text/javascript"
193
+ }
194
+ }
195
+ },
196
+ "view": {
197
+ "html": "<div id=\"root\" data-stillmade-layout=\"own\"></div>\n",
198
+ "css": "/* ============================================================\n StillMade AI — Foundations applied to the AI Video Workflow app.\n Two surfaces only: cream (#F7F0DB) and ink (#0A0A0A).\n Everything else is a tonal shift of one of those two.\n Dark is the default; [data-mode=\"light\"] flips the surface.\n ============================================================ */\n/* When the licensed Balgin Skinny Italic files are available, drop\n them into client/public/fonts and uncomment this block — the\n display face points at \"Balgin\" first and switches automatically. */\n/*\n@font-face {\n font-family: \"Balgin\";\n src: url(\"/fonts/Balgin-SkinnyItalic.woff2\") format(\"woff2\");\n font-weight: 100;\n font-style: italic;\n font-display: swap;\n}\n*/\n\n*, *::before, *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\n/* ============================================================\n Animated Studio — fixed slate theme (ignores light/dark).\n :root[...] specificity beats [data-mode], so it wins in both modes.\n ============================================================ */\n:root[data-anim-theme=\"1\"]:not([data-mode=\"white\"]) {\n color-scheme: dark;\n --bg: #0A0D13;\n --bg-elev-1: #0F141C;\n --bg-elev-2: #1A2230;\n --bg-inset: #0F141C;\n --fg: #D8DEE8;\n --fg-muted: #8B98AC;\n --fg-subtle: #64748B;\n --fg-on-accent: #0A0D13;\n --border-tok: #232D3D;\n --border-strong: #303D50;\n --divider: #1A2230;\n --hover-wash: rgba(139, 152, 172, 0.08);\n --press-wash: rgba(139, 152, 172, 0.16);\n --selection-bg: #303D50;\n --selection-fg: #D8DEE8;\n\n --bg-primary: #0A0D13;\n --bg-secondary: #0F141C;\n --bg-tertiary: #1A2230;\n --bg-elevated: #1A2230;\n --bg-hover: #232D3D;\n --border: #232D3D;\n --border-light: #303D50;\n --accent: #8B98AC;\n --accent-hover: #B6C0CE;\n --accent-dim: rgba(139, 152, 172, 0.12);\n --accent-glow: rgba(139, 152, 172, 0.25);\n --accent-contrast: #0A0D13;\n --editor-standout-bg: #D8DEE8;\n --editor-standout-fg: #0A0D13;\n --text-primary: #D8DEE8;\n --text-secondary: #8B98AC;\n --text-muted: #64748B;\n --success: #8B98AC;\n --warning: #B6C0CE;\n --danger: #B6C0CE;\n}\n\nhtml, body {\n height: 100%;\n width: 100%;\n overflow: hidden;\n background: var(--bg);\n color: var(--fg);\n font-family: var(--font-body);\n font-size: 14px;\n line-height: 1.6;\n letter-spacing: var(--tr-body);\n -webkit-font-smoothing: antialiased;\n text-rendering: optimizeLegibility;\n}\n\n#root {\n height: 100%;\n width: 100%;\n overflow: hidden;\n}\n\n/* App shell fills the dynamically-visible viewport. 100dvh (not 100vh) so the\n mobile browser's collapsing toolbar doesn't push the inner scroll container —\n and its last row of content — below the visible area where it can't be\n reached. vh fallback first for browsers without dvh. */\n.sm-app-shell { height: 100vh; height: 100dvh; }\n\n/* Full-screen mobile nav menu enters with a composited fade+slide (rule 6). */\n@keyframes sm-navmenu-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }\n.mobile-nav-menu { animation: sm-navmenu-in 200ms cubic-bezier(0.22, 1, 0.36, 1); }\n\n::selection { background: var(--selection-bg); color: var(--selection-fg); }\n\n/* Display lockups — always italic, always thin. This is the brand. */\n.display, h1, h2 {\n font-family: var(--font-display);\n font-style: italic;\n font-weight: 200;\n line-height: var(--lh-tight);\n letter-spacing: var(--tr-display);\n margin: 0;\n}\n.display { line-height: var(--lh-display); }\nh3, h4 {\n font-family: var(--font-body);\n font-weight: 500;\n line-height: var(--lh-tight);\n letter-spacing: -0.01em;\n margin: 0;\n}\n\n.eyebrow {font-size: 11px;\n color: var(--fg-muted);\n font-weight: 500;}\n\ncode, pre, .mono {\n font-family: var(--font-mono);\n font-size: 0.92em;\n}\n/* Listing IDs are interface text, not code. */\n.listing-identity code { font-family: inherit; font-size: inherit; }\n\nbutton {\n font-family: var(--font-body);\n cursor: pointer;\n border: none;\n outline: none;\n letter-spacing: var(--tr-body);\n}\n\ntextarea, input, select {\n font-family: var(--font-body);\n outline: none;\n}\n\n/* ─── Native <select> → app theme, applied GLOBALLY to every select (now + future),\n so individual call sites never need touching. This themes the CLOSED control via the\n theme tokens while keeping the browser's NATIVE open/close + selection behavior fully\n intact (so picking an option always works). The OPEN popup is drawn by the OS —\n macOS/Safari can't be CSS-themed — and `option` colors below theme it only where the\n browser honors them (Chrome/Firefox on Win/Linux). A truly themed popup everywhere\n needs a custom dropdown component, not native <select>. ─── */\nselect {\n background-color: var(--bg-elev-1);\n color: var(--text-primary);\n border: 1px solid var(--border);\n border-radius: var(--r-2);\n}\nselect option,\nselect optgroup {\n background-color: var(--bg-elevated);\n color: var(--text-primary);\n}\nselect option:checked { font-weight: 600; }\n\n/* ─── ThemedSelect — a custom dropdown whose OPEN popup is themed too (the one thing a\n native <select> can't do on macOS). The trigger mirrors the native `select` look above,\n so a converted call site is visually identical when closed; the popup + options are ours,\n including per-option disabled state and an \"ⓘ\" info tooltip (e.g. Coming Soon). ─── */\n.tsel-trigger {\n display: inline-flex; align-items: center; justify-content: space-between; gap: 8px;\n background-color: var(--bg-elev-1); color: var(--text-primary);\n border: 1px solid var(--border); border-radius: var(--r-2);\n font-family: var(--font-body); font-size: inherit; padding: 6px 10px;\n cursor: pointer; text-align: left;\n}\n.tsel-trigger:hover { border-color: var(--border-strong); }\n.tsel-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }\n.tsel-trigger:disabled { opacity: 0.5; cursor: default; }\n.tsel-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.tsel-caret { flex: none; font-size: 10px; opacity: 0.7; }\n.tsel-pop {\n /* Above EVERY overlay that can contain a select, not just app dialogs (100000):\n the mobile BottomSheet is 100050, TemplateStudio 100090 and the PreviewGrid\n lightbox 100095. At the old 100010 the popup painted UNDERNEATH the sheet\n that opened it, so every dropdown inside a mobile sheet (motion easing,\n font, position, word-reveal swap/emphasis/trail layout) and the Whoosh\n transition's Direction slot in TemplateStudio silently did nothing. */\n z-index: 100100;\n max-height: 260px; overflow-y: auto;\n background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--r-2);\n box-shadow: 0 8px 28px rgba(0, 0, 0, 0.28); padding: 4px;\n}\n.tsel-opt {\n display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-radius: 6px;\n color: var(--text-primary); cursor: pointer; font-size: 13px; white-space: nowrap;\n}\n.tsel-opt[data-active=\"true\"] { background: var(--bg-elev-1); }\n.tsel-opt[aria-selected=\"true\"] { font-weight: 600; }\n.tsel-opt[aria-disabled=\"true\"] { color: var(--text-muted); cursor: default; }\n.tsel-opt[aria-disabled=\"true\"][data-active=\"true\"] { background: transparent; }\n.tsel-check { flex: none; width: 12px; font-size: 11px; opacity: 0.8; }\n.tsel-opt-label { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }\n.tsel-note {\n position: relative;\n flex: none; width: 16px; height: 16px; font-size: 11px; cursor: help;\n color: var(--text-muted); border: 1px solid var(--border); border-radius: 999px;\n display: inline-flex; align-items: center; justify-content: center;\n}\n.tsel-note:hover { color: var(--text-primary); border-color: var(--border-strong); }\n/* Info tooltip (e.g. \"Coming Soon\"). Portaled to <body> and positioned to the RIGHT of the ⓘ\n so the popup's overflow can never clip it. */\n.tsel-tip {\n z-index: 100110; /* one step above .tsel-pop — it annotates an option inside it */\n background: var(--text-primary); color: var(--bg-elevated);\n padding: 4px 8px; border-radius: 5px; font-size: 11px; font-weight: 600;\n max-width: min(260px, calc(100vw - 16px)); box-sizing: border-box;\n white-space: normal; pointer-events: none;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);\n}\n@media (max-width: 767.98px), (max-height: 480px) and (pointer: coarse) {\n .tsel-opt { min-height: 44px; box-sizing: border-box; font-size: 14px; }\n .tsel-note { width: 44px; height: 44px; flex: 0 0 44px; font-size: 14px; }\n}\n\n.video-editor input,\n.video-editor select,\n.video-editor textarea {\n width: 100%;\n min-width: 0;\n background: var(--bg-inset);\n color: var(--fg);\n border: var(--bw-1) solid var(--border-strong);\n border-radius: var(--r-2);\n padding: 7px 8px;\n font-family: var(--font-body);\n font-size: 12px;\n accent-color: var(--signal);\n}\n\n.video-editor,\n.mobile-editor-root {\n user-select: none;\n -webkit-user-select: none;\n /* Kill the iOS long-press callout (Copy / Search) and the selection\n magnifier so a touch drag/trim/long-press on the timeline, labels, or\n buttons can't accidentally select UI text. Inputs re-enable both below. */\n -webkit-touch-callout: none;\n}\n\n.video-editor input,\n.video-editor select,\n.video-editor textarea,\n.video-editor [contenteditable=\"true\"],\n.mobile-editor-root input,\n.mobile-editor-root select,\n.mobile-editor-root textarea,\n.mobile-editor-root [contenteditable=\"true\"] {\n user-select: text;\n -webkit-user-select: text;\n -webkit-touch-callout: default;\n}\n\n.video-editor input[type=\"range\"] {\n padding: 0;\n border: 0;\n background: transparent;\n}\n\n.video-editor input[type=\"checkbox\"] {\n width: auto;\n min-width: auto;\n}\n\n.video-editor select:focus,\n.video-editor input:focus,\n.video-editor textarea:focus {\n border-color: var(--signal);\n box-shadow: 0 0 0 1px var(--signal-dim);\n}\n\n/* Focus — always visible, always the signal color */\n:focus-visible {\n outline: 2px solid var(--signal);\n outline-offset: 2px;\n}\n\n::-webkit-scrollbar {\n width: 6px;\n height: 6px;\n}\n::-webkit-scrollbar-track { background: transparent; }\n::-webkit-scrollbar-thumb {\n background: var(--border-strong);\n border-radius: var(--r-pill);\n}\n::-webkit-scrollbar-thumb:hover { background: var(--fg-subtle); }\n\n/* Hide the scrollbar chrome while keeping the element scrollable (wheel /\n trackpad). Used on the editor's left panel + tab rail for a clean column. */\n.sm-hide-scrollbar { scrollbar-width: none; -ms-overflow-style: none; }\n.sm-hide-scrollbar::-webkit-scrollbar { width: 0; height: 0; display: none; }\n\n/* ============================================================\n Buttons — primary is fg/bg inversion, not a color.\n Sharp by default; pill is a deliberate touch affordance.\n ============================================================ */\n.btn {\n display: inline-flex;\n align-items: center;\n gap: var(--s-2);\n padding: var(--s-2) var(--s-4);\n border-radius: var(--r-pill);\n font-size: 13px;\n font-weight: 500;\n letter-spacing: var(--tr-body);\n transition: transform var(--dur-fast) var(--ease-out),\n background-color var(--dur-fast) var(--ease-out),\n border-color var(--dur-fast) var(--ease-out),\n color var(--dur-fast) var(--ease-out),\n opacity var(--dur-fast) var(--ease-out);\n}\n.btn:active { transform: scale(0.98); }\n\n.btn-primary {\n background: var(--fg);\n color: var(--bg);\n}\n.btn-primary:hover { background: var(--fg); opacity: 0.86; }\n.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }\n\n.btn-secondary {\n background: transparent;\n color: var(--fg);\n border: var(--bw-1) solid var(--border-strong);\n}\n.btn-secondary:hover { background: var(--hover-wash); border-color: var(--fg); }\n\n.btn-ghost {\n background: transparent;\n color: var(--fg-muted);\n padding: 6px 10px;\n}\n.btn-ghost:hover { background: var(--hover-wash); color: var(--fg); }\n\n.btn-icon {\n background: transparent;\n color: var(--fg-muted);\n padding: 5px;\n border-radius: var(--r-2);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n transition: background-color var(--dur-fast) var(--ease-out),\n color var(--dur-fast) var(--ease-out),\n transform var(--dur-fast) var(--ease-out);\n}\n.btn-icon:hover { background: var(--hover-wash); color: var(--fg); }\n.btn-icon:active { transform: scale(0.98); }\n.btn-icon:disabled { opacity: 0.4; cursor: not-allowed; }\n\n.typing-indicator {\n display: flex;\n gap: 4px;\n align-items: center;\n padding: 4px 0;\n}\n.typing-indicator span {\n width: 6px;\n height: 6px;\n border-radius: var(--r-pill);\n background: var(--fg-subtle);\n animation: bounce 1.2s infinite;\n}\n.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }\n.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }\n\n@keyframes bounce {\n 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }\n 30% { transform: translateY(-5px); opacity: 1; }\n}\n\n@keyframes fadeIn {\n from { opacity: 0; transform: translateY(6px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n@keyframes spin {\n to { transform: rotate(360deg); }\n}\n\n@keyframes pulse {\n 0%, 100% { opacity: 0.4; transform: scale(0.9); }\n 50% { opacity: 1; transform: scale(1.1); }\n}\n\n/* Toast slide-in for ToastHost (services/toast.js) */\n@keyframes toastIn {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* Q3 — keyboard focus rings (focus-visible only, so mouse users don't see them) */\nbutton:focus-visible,\n[role=\"button\"]:focus-visible,\na:focus-visible,\ninput:focus-visible,\nselect:focus-visible,\ntextarea:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 2px;\n border-radius: 4px;\n}\n\n/* Typing fields never show a focus outline or ring (docs/UI_AESTHETIC.md):\n the caret marks focus. Buttons, links, switches and checkboxes keep theirs.\n !important so component focus-visible rules cannot bring the ring back. */\ninput:not([type=\"checkbox\"]):not([type=\"radio\"]):not([type=\"range\"]):not([type=\"color\"]):not([type=\"file\"]):not([type=\"button\"]):not([type=\"submit\"]):not([type=\"reset\"]):is(:focus, :focus-visible),\ntextarea:is(:focus, :focus-visible),\nselect:is(:focus, :focus-visible),\n[contenteditable]:not([contenteditable=\"false\"]):is(:focus, :focus-visible) {\n outline: none !important;\n}\n\n/* Skeleton shimmer for P2 loaders */\n@keyframes skeletonShimmer {\n 0% { background-position: -200% 0; }\n 100% { background-position: 200% 0; }\n}\n.skeleton {\n background: linear-gradient(90deg,\n rgba(255,255,255,0.03) 0%,\n rgba(255,255,255,0.10) 50%,\n rgba(255,255,255,0.03) 100%);\n background-size: 200% 100%;\n animation: skeletonShimmer 1.4s ease-in-out infinite;\n border-radius: 6px;\n}\n\n/* Node-state visuals (E1) — applied to NodeShell wrapper.\n States: empty (dashed ghost) / generating (shimmer ring) / failed (red stripe) / complete */\n/* opacity:0.78 removed — CSS opacity cascades to children and faded the\n image too. Dashed border alone is the \"pending\" signal now. */\n.node-state-empty { border-style: dashed !important; }\n/* The pulsing glow animates OPACITY on a pseudo-element (compositor-only)\n instead of keyframing box-shadow, which repainted the node's full shadow\n region on the main thread every frame for the whole generation — with\n batch generation pulsing many nodes at once. Same look: constant 2px\n accent ring + a breathing 18px glow. */\n.node-state-generating { box-shadow: 0 0 0 2px var(--accent); position: relative; }\n.node-state-generating::after {\n content: '';\n position: absolute;\n inset: -2px;\n border-radius: inherit;\n box-shadow: 0 0 18px rgba(247, 200, 50, 0.55);\n animation: nodePulse 1.6s ease-in-out infinite;\n pointer-events: none;\n will-change: opacity;\n}\n@keyframes nodePulse {\n 0%, 100% { opacity: 0.36; }\n 50% { opacity: 1; }\n}\n.node-state-warning { position: relative; border-color: var(--attention-warning) !important; }\n.node-state-warning::after {\n content: ''; position: absolute; inset: -2px; border-radius: inherit; pointer-events: none;\n box-shadow: 0 0 0 1px var(--attention-warning), 0 0 14px color-mix(in srgb, var(--attention-warning) 40%, transparent);\n}\n.node-state-failed { border-top: 4px solid #d04848 !important; }\n.node-state-failed::before {\n content: '⚠ '; color: #d04848; font-weight: 800;\n position: absolute; top: 6px; left: 8px; font-size: 12px; z-index: 2;\n pointer-events: none;\n}\n\n/* React Flow overrides */\n.react-flow__node { cursor: default; }\n/* Promote layers only WHILE a gesture is live (xyflow toggles these classes\n automatically at drag start/end) — the dragged card and the panning\n viewport move on the compositor instead of repainting their 8-layer\n shadows every frame. No idle memory cost: the hint drops with the class. */\n.react-flow__node.dragging { will-change: transform; }\n.react-flow__pane.dragging .react-flow__viewport { will-change: transform; }\n\n/* ── 3D node extrusion (StillMade design bundle) ────────────────────\n Stacked box-shadow that follows the corner radius, plus an ambient\n cast. Compositor-only: no per-frame JS, no extra DOM. Capped at 6\n solid layers so paint cost stays bounded on large scenes (147 nodes).\n Excludes layout-only node types (scene groups, shot labels, sticky\n notes) which aren't cards. */\n.react-flow__node:not(.react-flow__node-sceneGroupNode):not(.react-flow__node-shotLabelNode):not(.react-flow__node-stickyNoteNode):not(.react-flow__node-transitionNode) {\n border-radius: 10px;\n box-shadow:\n 1px 1px 0 var(--node-side, #d8cfb3),\n 2px 2px 0 var(--node-side, #d8cfb3),\n 3px 3px 0 var(--node-side, #d8cfb3),\n 4px 4px 0 var(--node-side, #d8cfb3),\n 5px 5px 0 var(--node-side, #d8cfb3),\n 6px 6px 0 var(--node-side, #d8cfb3),\n 7px 7px 0 var(--node-edge, rgba(10,10,10,0.18)),\n 14px 16px 18px -4px rgba(10,10,10,0.18);\n /* No transition / hover-lift / drag-lift — animating transform on\n react-flow's already-transformed wrapper compounds onto its pan\n matrix and makes pans/drags feel laggy. Static shadow only. */\n}\n[data-mode=\"dark\"] .react-flow__node:not(.react-flow__node-sceneGroupNode):not(.react-flow__node-shotLabelNode):not(.react-flow__node-stickyNoteNode):not(.react-flow__node-transitionNode) {\n --node-side: #1f1d18;\n --node-edge: rgba(247, 240, 219, 0.18);\n}\n/* Zoom controls laid out as a 2×2 square (positioned above the Feedback pill\n in JS). Overrides React Flow v12's default flex-column. */\n.react-flow__controls {\n display: grid !important;\n grid-template-columns: repeat(2, 1fr) !important;\n}\n.react-flow__controls button {\n background: var(--bg-elev-2) !important;\n border-color: var(--border-tok) !important;\n color: var(--fg-muted) !important;\n fill: var(--fg-muted) !important;\n}\n.react-flow__controls button:hover {\n background: var(--ink-300) !important;\n}\n.react-flow__edge-path {\n stroke: var(--border-strong);\n}\n\n/* While actively panning a large graph, skip painting the edge layer (the main\n big-graph pan cost). Edges stay mounted — only paint is suppressed — so they\n reappear instantly with no remount when motion stops. See WorkflowCanvas\n markPanning / EDGE_PAN_HIDE_MIN. */\n.react-flow.rf-edges-panning .react-flow__edges {\n display: none;\n}\n\n.react-flow__node.node-controls-locked input,\n.react-flow__node.node-controls-locked textarea,\n.react-flow__node.node-controls-locked select,\n.react-flow__node.node-controls-locked button,\n.react-flow__node.node-controls-locked [role=\"button\"] {\n pointer-events: none !important;\n}\n\n.react-flow__node.node-controls-locked {\n cursor: grab;\n}\n\n.react-flow__node.node-controls-locked:active {\n cursor: grabbing;\n}\n\n.react-flow__node.scene-add-available-node {\n filter: drop-shadow(0 0 10px rgba(247,240,219,0.22));\n}\n\n.react-flow__node.scene-add-available-node > div {\n outline: 1px solid var(--border-strong);\n outline-offset: 4px;\n border-radius: var(--r-3);\n}\n\n.react-flow__node.scene-add-available-node::after {\n content: \"\";\n position: absolute;\n inset: -8px;\n border: 1px dashed var(--border-strong);\n border-radius: var(--r-3);\n pointer-events: none;\n}\n.react-flow__node.node-replace-available {\n filter: drop-shadow(0 0 10px rgba(247,240,219,0.22));\n}\n\n.react-flow__node.node-replace-available > div {\n outline: 1px solid var(--border-strong);\n outline-offset: 4px;\n border-radius: var(--r-3);\n}\n\n.react-flow__node.node-replace-available::after {\n content: \"\";\n position: absolute;\n inset: -8px;\n border: 1px dashed var(--border-strong);\n border-radius: var(--r-3);\n pointer-events: none;\n}\n\nselect option {\n background: var(--bg-elev-2);\n color: var(--fg);\n}\n\n.spinner {\n width: 16px;\n height: 16px;\n border: 2px solid var(--border-tok);\n border-top-color: var(--fg);\n border-radius: var(--r-pill);\n animation: spin 0.7s linear infinite;\n}\n\n.media-upload-preview:hover .media-upload-overlay {\n opacity: 1 !important;\n}\n\n/* Reduced motion — respect it everywhere */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 1ms !important;\n transition-duration: 1ms !important;\n }\n}\n\n/* All Assets grid: overlay shows on hover via CSS so the parent\n doesn't need a hoveredId state that re-renders the whole list. */\n.assets-card .assets-card-overlay { opacity: 0; transition: opacity 0.15s; }\n.assets-card:hover .assets-card-overlay { opacity: 1; }\n\n/* YouTube hub — Video performance rows: soft accent glow on hover so the rows\n feel clickable. Inset (not outer) shadow because the table clips overflow. */\n.yt-video-row { transition: background 0.12s ease, box-shadow 0.12s ease, color 0.12s ease; }\n.yt-video-row:hover {\n background: var(--hover-wash) !important;\n box-shadow: inset 2px 0 0 var(--accent), inset 0 0 14px var(--accent-glow);\n color: var(--text-primary);\n}\n\n/* Chat bubble text selection — the global ::selection token (which uses\n ink-600 in light mode) collides with the user bubble's dark accent\n background, making highlights invisible. Force the signal/amber color\n here so highlights pop on both user (dark bubble) and assistant\n (cream bubble) regardless of theme. */\n.chat-bubble *::selection { background: var(--signal); color: #0A0A0A; }\n.chat-bubble *::-moz-selection { background: var(--signal); color: #0A0A0A; }\n\n/* Chat bubble copy button — visible on hover/focus, and stays visible\n while the cursor is over the button itself (otherwise moving from\n bubble onto button would hide it before you could click). */\n.chat-bubble:hover .chat-bubble-copy,\n.chat-bubble:focus-within .chat-bubble-copy,\n.chat-bubble-copy:hover,\n.chat-bubble-copy:focus { opacity: 1 !important; }\n/* Subtle hover/active feedback so the user knows it's clickable. */\n.chat-bubble-copy:hover { background: var(--bg-elevated-2, var(--bg-elevated)); color: var(--text-primary) !important; }\n.chat-bubble-copy:active { transform: scale(0.94); }\n\n/* ── StillMade logo lockup — hover animation ──────────────────────────────\n Hovering the top-left lockup turns the isometric cube left-to-right around its\n vertical axis (a 3D rotateY) with a soft currentColor glow; it turns back on\n leave. Reduced-motion users get just a gentle scale + glow, no rotation. */\n.sm-brand .sm-brandmark {\n transform-origin: 50% 50%;\n transition: transform 760ms cubic-bezier(0.2, 0.8, 0.2, 1), filter 280ms ease;\n}\n.sm-brand:hover .sm-brandmark {\n transform: perspective(340px) rotateY(360deg) scale(1.06);\n filter: drop-shadow(0 0 7px color-mix(in srgb, currentColor 45%, transparent));\n will-change: transform, filter;\n}\n/* Wordmark resting position + a subtle lift on hover (a small nudge — gentler\n than the design's). Resting transform lives here (not inline) so :hover can\n override it. */\n.sm-brand .sm-word {\n transform: translate(-4px, 0px);\n transition: transform 300ms cubic-bezier(0.2, 0.8, 0.2, 1);\n}\n.sm-brand:hover .sm-word { transform: translate(-4px, -1px); will-change: transform; }\n@media (prefers-reduced-motion: reduce) {\n .sm-brand .sm-brandmark { transition: transform 240ms ease, filter 240ms ease; }\n .sm-brand:hover .sm-brandmark { transform: scale(1.06); }\n .sm-brand:hover .sm-word { transform: translate(-4px, 0px); }\n}\n\n/* ── Mobile UI ergonomics ───────────────────────────────────────────────────\n Scoped to body.sm-mobile-ui, which App.jsx adds ONLY when the user chose the\n mobile site on a small screen, including a rotated touch phone. Ordinary\n desktop windows and the continue-anyway mode are unaffected.\n\n The app is inline-styled, so these few universal touch fixes are applied here\n once instead of edited into every input across every screen:\n - 16px text fields stop iOS from zooming the page on focus (needs !important\n to beat the inline font sizes).\n - comfortable minimum tap target on buttons/selects. */\nbody.sm-mobile-ui input,\nbody.sm-mobile-ui textarea,\nbody.sm-mobile-ui select {\n font-size: 16px !important;\n}\nbody.sm-mobile-ui button,\nbody.sm-mobile-ui select,\nbody.sm-mobile-ui [role=\"button\"] {\n min-height: 44px;\n}\n/* WebKit can ignore min-height on its native one-line select. Keep the\n physical control touch-sized and let enlarged text make it taller. */\nbody.sm-mobile-ui select:not([multiple]):not([size]) {\n height: max(44px, 2.75em);\n}\n/* Icon-only / chip-sized controls opt out of the min height so dense toolbars\n (timeline, inspector) don't balloon. */\nbody.sm-mobile-ui button.btn-icon,\nbody.sm-mobile-ui button[data-compact],\nbody.sm-mobile-ui .no-min-tap {\n min-height: 0;\n}\n/* YouTube hub analytics rows are dense multi-column grids; keep their columns\n readable and let the table scroll horizontally instead of squishing on a\n phone (the table container is overflow-x:auto). */\nbody.sm-mobile-ui .yt-video-row {\n min-width: 560px;\n}\n\n/* Frame view: the Editor fills the space StillMade gives its Step (at least 560 pixels tall). */\n#root { height: max(560px, var(--stillmade-space-height, 100vh)); }\n/* Text wraps as on StillMade's own pages (frame views break long words anywhere by default). */\nhtml { overflow-wrap: normal; }\n\n/* Stylesheets the screen's components import. */\n.bs-private-dialog{box-sizing:border-box;margin:auto;width:min(720px,calc(100vw - 32px));max-height:calc(100dvh - 32px);padding:0;border:1px solid var(--border);border-radius:var(--r-3);background:var(--bg-primary);color:var(--text-primary);font:16px/1.6 var(--font-body);overflow:auto;overflow-wrap:anywhere}\n.bs-private-dialog::backdrop{background:var(--scrim,rgba(0,0,0,.4))}\n.bs-private-dialog>header,.bs-private-dialog>footer{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:20px 24px;background:var(--bg-primary)}\n.bs-private-dialog>header{border-bottom:1px solid var(--border);position:sticky;top:0;z-index:1}.bs-private-dialog>header>div{min-width:0}.bs-private-dialog>header p{margin:4px 0 0;font-size:14px;color:var(--text-secondary)}\n.bs-private-dialog h2{margin:0;font:italic 200 30px/1.2 var(--font-display);letter-spacing:var(--tr-display)}\n.bs-private-body{padding:20px 24px}.bs-private-body>p:first-child{margin-top:0}.bs-private-dialog fieldset{min-width:0;border:1px solid var(--border);border-radius:var(--r-3);padding:16px}.bs-private-dialog legend{padding:0 6px;font-size:14px}\n.bs-private-check{display:flex;align-items:flex-start;gap:12px;margin:14px 0}.bs-private-check input{width:18px;height:18px;flex-shrink:0;margin:4px 0 0;accent-color:var(--fg)}.bs-private-note{font-size:14px;color:var(--text-secondary)}\n.bs-private-dialog>footer{position:sticky;bottom:0;border-top:1px solid var(--border);justify-content:flex-end;z-index:1}.bs-private-dialog button,.bs-team-library button{font:inherit;max-width:100%;padding:8px 16px;border:1px solid var(--border-strong);border-radius:var(--r-pill);background:transparent;color:inherit;cursor:pointer}.bs-private-dialog button:disabled{opacity:.5;cursor:default}.bs-private-dialog .bs-private-primary{background:var(--fg);color:var(--bg-primary);border-color:var(--fg)}.bs-private-dialog :is(button,input):focus-visible{outline:2px solid var(--signal);outline-offset:3px}\n.bs-private-review{width:min(1100px,calc(100vw - 32px))}.bs-private-review .bs-import-review{margin:0}.bs-private-review .bs-import-review input[type=checkbox]{width:auto}.bs-private-review .bs-actions{position:sticky;bottom:0;background:var(--bg-secondary);border-top:1px solid var(--border);padding:12px 0}.bs-private-review .bs-actions button{font-size:16px}\n.bs-team-library{border-top:1px solid var(--border);margin-top:28px;padding-top:20px}.bs-team-library>header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px}.bs-team-library h2{margin:0}.bs-team-library .bs-card{border:1px solid var(--border);background:var(--bg-secondary)}.bs-team-library h3{margin:0;font-weight:500}.bs-team-library .bs-card button{display:block;margin-top:16px}\n@media(max-width:600px){.bs-private-dialog{width:calc(100vw - 16px);max-height:calc(100dvh - 16px)}.bs-private-dialog>header,.bs-private-dialog>footer,.bs-private-body{padding:16px}.bs-private-dialog>header{align-items:flex-start}.bs-private-dialog h2{font-size:26px}.bs-private-dialog>header>button{flex-shrink:0}.bs-team-library>header{flex-direction:column}.bs-private-review .bs-import-review{padding:16px}.bs-private-review .bs-private-body{padding:8px}.bs-private-dialog :is(input,select,textarea){font-size:16px}}\n\n.bs-private-check span>small{display:block}\n\n.publish-build-review{width:min(760px,calc(100vw - 32px))}\n.publish-build-review .bs-private-body{display:grid;gap:20px}\n.publish-build-review h3{margin:0 0 8px;font-size:18px;font-weight:600}\n.publish-build-review p{margin:8px 0;color:var(--text-secondary)}\n.publish-build-review details{border-top:1px solid var(--border);padding-top:16px}\n.publish-build-review summary{cursor:pointer;font-weight:600}\n.publish-build-review form>footer{display:flex;gap:12px;justify-content:flex-end;position:sticky;bottom:0;padding:18px 24px;border-top:1px solid var(--border);background:var(--bg-primary)}\n.publish-build-review .bs-private-check{cursor:pointer}\n@media(max-width:540px){.publish-build-review form>footer{flex-direction:column;padding:16px}.publish-build-review form>footer button{width:100%}}\n\n.project-type-start-review .bs-private-body>label{display:grid;gap:8px}.project-type-start-review :is(input,select,textarea){box-sizing:border-box;width:100%;padding:10px 12px;background:var(--bg-primary);color:var(--text-primary);border:1px solid var(--border);border-radius:var(--r-3);font:inherit}.project-type-start-review textarea{resize:vertical}\n\n.block-audio-library{min-width:0;margin-top:20px;padding-top:16px;border-top:1px solid var(--border-tok);color:var(--fg);font:16px/1.5 var(--font-body);overflow-wrap:anywhere}\n.block-audio-library h3{font:200 italic 24px/1.2 var(--font-display);margin:0 0 14px}\n.bal-recording{display:grid;gap:8px;min-width:0;padding:12px 0}.bal-recording+.bal-recording{border-top:1px solid var(--border-tok)}.bal-recording p{margin:0;color:var(--fg-muted);font-size:13px}\n.bal-recording audio{display:block;min-width:0;width:100%;max-width:100%;height:44px}.bal-recording button{width:100%;min-width:0;font:inherit;padding:9px 14px;border:1px solid var(--border-tok);border-radius:var(--r-pill);background:var(--bg-elevated);color:var(--fg);cursor:pointer}.bal-recording button:disabled{opacity:.45;cursor:default}.bal-recording button:focus-visible{outline:2px solid var(--signal);outline-offset:3px}\n\n.timeline-edit-preview{margin:12px 0;padding:16px;border:1px solid var(--border-tok);border-radius:var(--r-card,12px);background:var(--bg-elevated);color:var(--fg);font:16px/1.5 var(--font-body);min-width:0;overflow-wrap:anywhere}\n.timeline-edit-preview figcaption{font:200 italic 24px/1.2 var(--font-display)}\n.timeline-edit-preview>p{color:var(--fg-muted);margin:10px 0;font-size:14px}\n.tep-change{padding:12px 0;border-top:1px solid var(--border-tok)}.tep-change>strong{display:block;margin-bottom:8px}\n.tep-row{display:grid;grid-template-columns:52px minmax(0,1fr);gap:6px 10px;align-items:center;margin:8px 0}.tep-row>small{grid-column:2;color:var(--fg-muted);font-size:12px}.tep-row>span{font-size:13px}\n.tep-lane{height:24px;position:relative;background:var(--bg);border-radius:4px;overflow:hidden;border:1px solid var(--border-tok)}.tep-clip{position:absolute;top:2px;bottom:2px;background:var(--signal);border:1px solid var(--fg-muted);border-radius:3px;min-width:2px}.tep-lane>small{position:absolute;inset:0;text-align:center;color:var(--fg-muted);font-size:12px}\n\n/* StillMade media player, photo button and full-screen viewer: option A \"Cream blocks\" (owner, November 2, 2026). Solid cream\n rounded squares with ink icons, a thick cream timeline, the time in the thin italic display face. The chrome sits on the\n picture, so it is the same in Light, Dark and White; the letterbox is ink. Rounded squares only, never circles\n (UI_AESTHETIC \"Icon preference\"). Everything that moves per frame moves by transform; nothing uses backdrop-filter.\n Built by media-player.js (attachMediaPlayer, openMediaViewer, attachPhotoExpand). */\n:where(.sm-player,.sm-viewer,.sm-expand-btn){--sm-cream:var(--cream-100,#F7F0DB);--sm-ink:var(--ink-600,#0A0A0A);--sm-gold:var(--signal,#E8B23A);--sm-out:var(--dur-fast,160ms);--sm-in:var(--dur-base,240ms);--sm-ease:var(--ease-out,cubic-bezier(.22,1,.36,1));--sm-spring:var(--ease-spring,cubic-bezier(.34,1.56,.64,1));--sm-display:var(--font-display,\"Balgin\",\"Hanken Grotesk\",ui-sans-serif,sans-serif)}\n\n/* ---------- The player box: shrinks to the video like a bare <video>; data-fill fills a box the caller sizes ---------- */\n.sm-player{position:relative;display:inline-block;vertical-align:top;max-width:100%;box-sizing:border-box;overflow:hidden;background:var(--sm-ink);color:var(--sm-cream);line-height:1;font-family:var(--font-body,\"Hanken Grotesk\",ui-sans-serif,system-ui,sans-serif);user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent;touch-action:manipulation;outline:none}\n.sm-player:focus-visible{outline:2px solid var(--sm-gold);outline-offset:2px}\n.sm-player>video{display:block;width:auto;max-width:100%;height:auto;max-height:inherit;margin:0 auto;object-fit:contain;background:transparent}\n.sm-player[data-fill]>video,.sm-player:fullscreen>video,.sm-player[data-stage]>video{width:100%;height:100%;max-width:none;max-height:none}\n.sm-player[data-fill]{display:block}\n.sm-player[data-idle]{cursor:none}\n:where(.sm-stage)>.sm-player{max-width:100%;max-height:100%;border-radius:var(--r-2,4px)}\n\n/* The controls layer covers the picture; only the bar takes the pointer, so a click on the picture reaches the box. */\n.sm-player-ui{position:absolute;inset:0;z-index:1;pointer-events:none}\n.sm-player-shade{position:absolute;left:0;right:0;bottom:0;height:120px;max-height:100%;background:linear-gradient(to top,rgba(10,10,10,.55),rgba(10,10,10,0))}\n.sm-player-bar{position:absolute;left:12px;right:12px;bottom:12px;display:flex;align-items:center;gap:10px;pointer-events:auto}\n.sm-player-shade,.sm-player-bar{transition:opacity var(--sm-in) var(--sm-ease)}\n/* November 2, 2026 (owner): hover only. data-away (no pointer over it, no keyboard focus in it, no recent tap) and data-idle\n (the mouse resting on a playing video) hide them; hidden controls take no pointer, so a tap reaches the picture. */\n.sm-player[data-away] .sm-player-shade,.sm-player[data-away] .sm-player-bar,.sm-player[data-idle] .sm-player-shade,.sm-player[data-idle] .sm-player-bar{opacity:0;pointer-events:none;transition-duration:var(--sm-out)}\n\n/* ---------- Cream squares (player, viewer) ---------- */\n:where(.sm-player,.sm-viewer) .sm-media-btn{appearance:none;-webkit-appearance:none;box-sizing:border-box;flex:none;display:grid;place-items:center;width:44px;height:44px;min-width:0;min-height:0;margin:0;padding:0;border:0;border-radius:var(--r-3,8px);background:var(--sm-cream);color:var(--sm-ink);box-shadow:none;font:inherit;line-height:1;text-decoration:none;cursor:pointer;transition:transform var(--sm-out) var(--sm-spring)}\n:where(.sm-player,.sm-viewer) .sm-media-btn svg{display:block;width:20px;height:20px}\n:is(.sm-player,.sm-viewer) .sm-media-btn.sm-media-btn.sm-media-btn-text{width:auto;padding:0 14px;font-size:14px;font-weight:600}\n/* November 2, 2026: the hover rules restate the cream, so a page's own button:hover (the listing pages, a Block's CSS) never\n washes the squares out. */\n@media (hover:hover){:where(.sm-player,.sm-viewer) .sm-media-btn:hover{transform:translateY(-1px);background:var(--sm-cream);color:var(--sm-ink)}}\n:where(.sm-player,.sm-viewer) .sm-media-btn:active{transform:scale(.94)}\n:is(.sm-player,.sm-viewer) .sm-media-btn.sm-media-btn:focus-visible{outline:2px solid var(--sm-gold);outline-offset:2px;border-radius:var(--r-3,8px)}\n/* November 2, 2026: a container's own button rule outranked the squares and drew them as outlined pills on a Step\n (.bs-step-workspace button:not(.btn-primary) is 0,2,1 and 0,3,1 on hover; .block-playground and .bs-shell buttons). The\n look is restated at 0,3,0, and the cream at 0,4,0 on any hover (a tap leaves :hover on touch); size, place and display\n stay in the rules above, so the narrow-player and viewer rules keep working. */\n:is(.sm-player,.sm-viewer) .sm-media-btn.sm-media-btn{min-width:0;min-height:0;padding:0;border:0;border-radius:var(--r-3,8px);background:var(--sm-cream);color:var(--sm-ink);box-shadow:none;font:inherit;line-height:1;text-decoration:none;cursor:pointer}\n:is(.sm-player,.sm-viewer) .sm-media-btn.sm-media-btn:hover{background:var(--sm-cream);color:var(--sm-ink)}\n.sm-player .sm-player-bar [data-control=\"fullscreen\"]{margin-left:auto}\n\n/* ---------- Volume (owner, November 2, 2026): a vertical cream line above the sound button, shown while the button or the line\n is hovered or focused, or while it is dragged; the same track, fill and square knob as the timeline. Mouse only. ---------- */\n.sm-player-sound{position:relative;display:flex;flex:none}\n.sm-player-volume{position:absolute;left:50%;bottom:100%;padding-bottom:10px;opacity:0;pointer-events:none;transform:translate(-50%,6px);transition:opacity var(--sm-out) var(--sm-ease),transform var(--sm-out) var(--sm-ease)}\n.sm-player-sound:hover .sm-player-volume,.sm-player-sound:focus-within .sm-player-volume,.sm-player-volume[data-drag]{opacity:1;pointer-events:auto;transform:translate(-50%,0);transition-duration:var(--sm-in)}\n.sm-player-volume-rail{position:relative;box-sizing:border-box;width:44px;height:128px;padding:14px 0;border-radius:var(--r-3,8px);background:rgba(10,10,10,.64);cursor:pointer;touch-action:none;outline:none}\n.sm-player-volume-rail:focus-visible{outline:2px solid var(--sm-gold);outline-offset:2px}\n.sm-player-volume-track{position:relative;width:6px;height:100%;margin:0 auto;border-radius:3px;background:rgba(247,240,219,.28);overflow:visible}\n.sm-player-volume-level{position:absolute;inset:0;border-radius:3px;background:var(--sm-cream);transform-origin:center bottom;transform:scaleY(1)}\n.sm-player-volume-head{position:absolute;inset:0;pointer-events:none;transform:translateY(-100%)}\n.sm-player-volume-knob{position:absolute;left:50%;bottom:0;width:16px;height:16px;margin:0 0 -8px -8px;border-radius:4px;background:var(--sm-cream);box-shadow:0 0 0 3px rgba(10,10,10,.35)}\n.sm-player-volume[data-drag] .sm-player-volume-head{will-change:transform}\n/* The player box clips, and the line opens 194 px above its bottom: under 200 px tall it is shorter, under 150 px it stays\n closed and the button only mutes (data-volume-room, measured by the player's ResizeObserver). */\n.sm-player[data-volume-room=\"short\"] .sm-player-volume-rail{height:84px;padding:12px 0}\n.sm-player[data-volume-room=\"none\"] .sm-player-volume{display:none}\n@media (hover:none){.sm-player-volume{display:none}}\n\n/* ---------- Timeline: 24 px to point at, a 6 px track that grows, a cream square knob, the time under the pointer ---------- */\n.sm-player-rail{position:relative;flex:1 1 auto;min-width:48px;height:24px;cursor:pointer;touch-action:none;outline:none}\n.sm-player-rail:focus-visible{outline:2px solid var(--sm-gold);outline-offset:2px;border-radius:var(--r-2,4px)}\n.sm-player-track{position:absolute;left:0;right:0;top:50%;height:6px;margin-top:-3px;border-radius:3px;background:rgba(247,240,219,.28);overflow:hidden;transition:transform var(--sm-out) var(--sm-ease)}\n.sm-player-buffered,.sm-player-played{position:absolute;inset:0;transform-origin:left center;transform:scaleX(0)}\n.sm-player-buffered{background:rgba(247,240,219,.18)}\n.sm-player-played{background:var(--sm-cream)}\n.sm-player-head{position:absolute;inset:0;pointer-events:none;transform:translateX(0)}\n.sm-player[data-playing] .sm-player-head,.sm-player[data-playing] .sm-player-played,.sm-player-rail[data-drag] .sm-player-head{will-change:transform}\n.sm-player-knob{position:absolute;top:50%;left:0;width:16px;height:16px;margin:-8px 0 0 -8px;border-radius:4px;background:var(--sm-cream);box-shadow:0 0 0 3px rgba(10,10,10,.35);transform:scale(0);transition:transform var(--sm-out) var(--sm-spring)}\n.sm-player-rail:hover .sm-player-knob,.sm-player-rail[data-drag] .sm-player-knob,.sm-player-rail:focus-visible .sm-player-knob{transform:scale(1)}\n.sm-player-rail:hover .sm-player-track,.sm-player-rail[data-drag] .sm-player-track{transform:scaleY(1.34)}\n.sm-player-tip{position:absolute;bottom:calc(100% + 4px);left:0;padding:3px 7px;border-radius:var(--r-2,4px);background:var(--sm-ink);border:1px solid rgba(247,240,219,.14);color:var(--sm-cream);font-size:12px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity var(--sm-out) var(--sm-ease)}\n.sm-player-rail:hover .sm-player-tip,.sm-player-rail[data-drag] .sm-player-tip{opacity:1}\n\n/* ---------- Time: \"0:12 / 0:34\", the total at 55% ---------- */\n.sm-player-time{flex:none;font-family:var(--sm-display);font-style:italic;font-weight:200;font-size:22px;line-height:1;letter-spacing:-.01em;font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--sm-cream);text-shadow:0 1px 8px rgba(10,10,10,.6)}\n:where(.sm-player,.sm-viewer) .sm-media-of{opacity:.55}\n\n/* ---------- Narrow players step down by measured width so the bar never overflows (November 2, 2026: a 320 px vertical\n result clipped full screen): under 400 px no total and a smaller clock, under 300 px no clock, under 240 px no sound,\n under 180 px only play and full screen. The timeline keeps at least 48 px. ---------- */\n.sm-player[data-size] .sm-player-bar{gap:8px}\n.sm-player[data-size] .sm-player-total{display:none}\n.sm-player[data-size] .sm-player-time{font-size:18px}\n.sm-player:is([data-size=\"compact\"],[data-size=\"tiny\"],[data-size=\"micro\"]) .sm-player-time{display:none}\n.sm-player:is([data-size=\"tiny\"],[data-size=\"micro\"]) [data-control=\"volume\"]{display:none}\n.sm-player[data-size=\"micro\"] .sm-player-bar>:not([data-control=\"play\"]):not([data-control=\"fullscreen\"]){display:none}\n\n/* ---------- Touch: 44 px targets ---------- */\n@media (hover:none){.sm-player-rail{height:44px}}\n\n/* ---------- Full screen, and theater mode where the page may not go full screen (top layer, safe areas) ---------- */\n.sm-player:fullscreen{width:100%;height:100%;max-width:none;max-height:none;border-radius:0}\n.sm-player[data-stage=\"theater\"]{position:fixed;inset:0;z-index:2147483000;display:block;width:auto;height:auto;max-width:none;max-height:none;margin:0;border:0;border-radius:0;padding:env(safe-area-inset-top,0px) env(safe-area-inset-right,0px) env(safe-area-inset-bottom,0px) env(safe-area-inset-left,0px);overflow:hidden;color:var(--sm-cream);background:var(--sm-ink)}\n.sm-player[data-stage=\"theater\"]::backdrop{background:var(--sm-ink)}\n.sm-player:fullscreen .sm-player-bar,.sm-player[data-stage] .sm-player-bar{left:max(12px,env(safe-area-inset-left,0px));right:max(12px,env(safe-area-inset-right,0px));bottom:max(12px,env(safe-area-inset-bottom,0px))}\n\n/* ---------- Photo button: the video's full-screen square in the same corner, bottom right. 44 px on photos at least 240 px\n wide, 32 px under that (data-size=\"small\"). Shown only while the photo is hovered or the button has keyboard focus, and for\n 3 s after a tap on touch (data-show); hidden, it takes no pointer, so the first tap lands on the photo. ---------- */\n.sm-expand-host{position:relative}\n.sm-expand-btn{position:absolute;right:12px;bottom:12px;z-index:2;box-sizing:border-box;display:grid;place-items:center;width:44px;height:44px;min-width:0;min-height:0;margin:0;padding:0;border:0;border-radius:var(--r-3,8px);background:var(--sm-cream);color:var(--sm-ink);box-shadow:none;line-height:1;cursor:pointer;opacity:0;pointer-events:none;transition:opacity var(--sm-out) var(--sm-ease),transform var(--sm-out) var(--sm-spring)}\n.sm-expand-btn svg{display:block;width:20px;height:20px}\n.sm-expand-btn[data-size=\"small\"]{right:6px;bottom:6px;width:32px;height:32px;border-radius:6px}\n.sm-expand-btn[data-size=\"small\"] svg{width:16px;height:16px}\n.sm-expand-btn[data-size=\"small\"]::before{content:\"\";position:absolute;inset:-6px}\n@media (hover:hover){:hover>.sm-expand-btn{opacity:1;pointer-events:auto}.sm-expand-btn:hover{transform:translateY(-1px);background:var(--sm-cream);color:var(--sm-ink)}}\n.sm-expand-btn:focus-visible,.sm-expand-btn[data-show]{opacity:1;pointer-events:auto}\n.sm-expand-btn:active{transform:scale(.94)}\n.sm-expand-btn.sm-expand-btn.sm-expand-btn:focus-visible{outline:2px solid var(--sm-gold);outline-offset:2px}\n/* The same guard as the squares above (November 2, 2026): the look at 0,3,0 and 0,4,0, never position, size or display. */\n.sm-expand-btn.sm-expand-btn.sm-expand-btn{min-width:0;min-height:0;padding:0;border:0;border-radius:var(--r-3,8px);background:var(--sm-cream);color:var(--sm-ink);box-shadow:none;font:inherit;line-height:1;cursor:pointer}\n.sm-expand-btn.sm-expand-btn.sm-expand-btn[data-size=\"small\"]{border-radius:6px}\n.sm-expand-btn.sm-expand-btn.sm-expand-btn:hover{background:var(--sm-cream);color:var(--sm-ink)}\n\n/* ---------- Viewer: a modal dialog on ink, the photo contained, cream squares to close and move ---------- */\n.sm-viewer{position:fixed;inset:0;box-sizing:border-box;width:100%;height:100%;max-width:none;max-height:none;margin:0;padding:0;border:0;overflow:hidden;background:var(--sm-ink);color:var(--sm-cream);font-family:var(--font-body,\"Hanken Grotesk\",ui-sans-serif,system-ui,sans-serif)}\n.sm-viewer[open]{display:grid;grid-template-rows:minmax(0,1fr) auto;animation:sm-viewer-in var(--sm-in) var(--sm-ease) both}\n.sm-viewer::backdrop{background:var(--ink-600,#0A0A0A)}\n@keyframes sm-viewer-in{from{opacity:0}}\n.sm-viewer [hidden],.sm-viewer .sm-media-btn[hidden]{display:none}\n.sm-viewer-stage{position:relative;display:grid;grid-template:minmax(0,1fr)/minmax(0,1fr);place-items:center;min-width:0;min-height:0;overflow:hidden;padding:max(12px,env(safe-area-inset-top,0px)) max(12px,env(safe-area-inset-right,0px)) 12px max(12px,env(safe-area-inset-left,0px))}\n/* The stage is one 1fr cell, so the photo is held to the screen in both directions (November 2, 2026: it ran off the bottom). */\n.sm-viewer-media{display:block;width:100%;height:100%;min-width:0;min-height:0;object-fit:contain}\n.sm-viewer-stage>.sm-player{width:100%;height:100%;max-width:100%;max-height:100%}\n/* What cannot open here (a file kept on another device) is a quiet tile with no words (November 2, 2026: \"notices, not\n prose\"); the app's single toast says why. */\n.sm-viewer-off,.sm-player-off{display:inline-grid;place-items:center;box-sizing:border-box;border:1px solid var(--border-tok,#58534D);border-radius:var(--r-2,4px);background:var(--bg-inset,#171716);color:var(--fg-muted,#BBB2A7)}\n.sm-viewer-off{width:min(60vw,320px);aspect-ratio:4/3}\n.sm-player-off{width:min(100%,320px);max-width:100%;aspect-ratio:16/9;vertical-align:top}\n.sm-viewer-off svg,.sm-player-off svg{display:block;width:24px;height:24px}\n.sm-viewer-top{position:absolute;top:max(12px,env(safe-area-inset-top,0px));right:max(12px,env(safe-area-inset-right,0px));z-index:2;display:flex;gap:10px}\n.sm-viewer .sm-viewer-nav{position:absolute;top:50%;margin-top:-22px;z-index:2}\n.sm-viewer .sm-viewer-previous{left:max(12px,env(safe-area-inset-left,0px))}\n.sm-viewer .sm-viewer-next{right:max(12px,env(safe-area-inset-right,0px))}\n.sm-viewer-foot{display:flex;align-items:baseline;justify-content:center;gap:12px;min-width:0;padding:12px max(12px,env(safe-area-inset-right,0px)) max(14px,env(safe-area-inset-bottom,0px)) max(12px,env(safe-area-inset-left,0px))}\n.sm-viewer-count{flex:none;font-family:var(--sm-display);font-style:italic;font-weight:200;font-size:24px;line-height:1;letter-spacing:-.01em;font-variant-numeric:tabular-nums;white-space:nowrap}\n.sm-viewer-name{min-width:0;font-size:13px;color:var(--fg-muted,#BBB2A7);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n\n@media (prefers-reduced-motion:reduce){\n .sm-player *,.sm-viewer,.sm-viewer *,.sm-expand-btn{transition:none!important;animation:none!important}\n}\n\n/* Preview shape tiles in the transport bar: a quiet row of outlines sized to the bar. */\n.ve-shape-choices{display:flex;flex-shrink:0;gap:2px}\n/* November 2, 2026: only unpicked tiles go transparent; the pick keeps the kit's ink fill, since\n a transparent pick drew its shape in the page colour (the kit's colour:var(--bg)) and vanished. */\n.ve-shape-choices>label{min-height:28px;padding:4px 6px}\n.ve-shape-choices>label:not(:has(input:checked)){border-color:transparent;background:transparent}\n.ve-shape-choices .sm-shape{--sm-shape-size:16px;border-width:1.5px}\n",
199
+ "javascript": "(function(){\"use strict\";var graph={\"view-BfB00tVc.js\":[\"react-Cd33N7Zq.js\",\"subscribeAnim-BqBgmjD4.js\",\"view-D6ZHgOjJ.js\"],\"MediaViewer-CkF9dtek.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"react-Cd33N7Zq.js\",\"media-player-B5xtn5y5.js\"],\"MobileEditor-4328GdaI.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"react-Cd33N7Zq.js\",\"editorAdds-CY8-DaYg.js\",\"editorTimelines-BHnIf5H0.js\",\"subscribeAnim-BqBgmjD4.js\",\"assetStore-4qfBrYoN.js\",\"liveOverlaysCanvas-DfCRS-HH.js\",\"view-D6ZHgOjJ.js\",\"boxOpacity-CxJqpgqJ.js\",\"bind-CkWXB-UM.js\",\"boxOpacity-9sGMQ6qd.js\"],\"PanelBlocksPanel-C9reO-vH.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"react-Cd33N7Zq.js\",\"editorAdds-CY8-DaYg.js\",\"contracts-C4-BRCqz.js\",\"subscribeAnim-BqBgmjD4.js\",\"liveOverlaysCanvas-DfCRS-HH.js\",\"view-D6ZHgOjJ.js\",\"boxOpacity-CxJqpgqJ.js\",\"boxOpacity-9sGMQ6qd.js\"],\"VideoEditor-BZqIpoNf.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"react-Cd33N7Zq.js\",\"editorAdds-CY8-DaYg.js\",\"editorTimelines-BHnIf5H0.js\",\"subscribeAnim-BqBgmjD4.js\",\"assetStore-4qfBrYoN.js\",\"liveOverlaysCanvas-DfCRS-HH.js\",\"view-D6ZHgOjJ.js\",\"boxOpacity-CxJqpgqJ.js\",\"bind-CkWXB-UM.js\",\"speedSample-BXiYiZ4E.js\",\"boxOpacity-9sGMQ6qd.js\",\"media-player-B5xtn5y5.js\"],\"_frame-empty_wasm_vtracer_wasm_vtracer_bg_wasm_url-C085yj93.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"assetStore-4qfBrYoN.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"contracts-C4-BRCqz.js\"],\"bind-CkWXB-UM.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"view-D6ZHgOjJ.js\",\"boxOpacity-CxJqpgqJ.js\",\"sharedVault-mMOG3PsG.js\"],\"boxOpacity-9sGMQ6qd.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"react-Cd33N7Zq.js\",\"editorAdds-CY8-DaYg.js\",\"editorTimelines-BHnIf5H0.js\",\"contracts-C4-BRCqz.js\",\"subscribeAnim-BqBgmjD4.js\",\"assetStore-4qfBrYoN.js\",\"type-workspace-U7I6uKdn.js\",\"liveOverlaysCanvas-DfCRS-HH.js\",\"view-D6ZHgOjJ.js\",\"boxOpacity-CxJqpgqJ.js\",\"bind-CkWXB-UM.js\",\"sharedVault-mMOG3PsG.js\",\"vectorizeClient-CSsuV7Ew.js\"],\"boxOpacity-CxJqpgqJ.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"react-Cd33N7Zq.js\",\"subscribeAnim-BqBgmjD4.js\"],\"browser-CGfns6yT.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"contracts-C4-BRCqz.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"editorAdds-CY8-DaYg.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"react-Cd33N7Zq.js\",\"subscribeAnim-BqBgmjD4.js\"],\"editorTimelines-BHnIf5H0.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"react-Cd33N7Zq.js\",\"editorAdds-CY8-DaYg.js\",\"contracts-C4-BRCqz.js\",\"subscribeAnim-BqBgmjD4.js\",\"type-workspace-U7I6uKdn.js\"],\"exportRender-BX-s25gI.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"boxOpacity-CxJqpgqJ.js\",\"bind-CkWXB-UM.js\",\"videoSource-_Ta7C4qY.js\"],\"exportRender-CIHSH4Lo.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"exportRender-BX-s25gI.js\"],\"favorites-BjGs2PS6.js\":[\"VideoEditor-BZqIpoNf.js\"],\"frameExtract-CpwOQS0p.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"imagetracer_v1.2.6-BBGerUPj.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"lamejs-Dix5aA0E.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"liveOverlaysCanvas-DfCRS-HH.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"media-player-B5xtn5y5.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"nativeRender-CFoK_eQg.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"boxOpacity-CxJqpgqJ.js\",\"videoSource-_Ta7C4qY.js\"],\"overlaysCanvas-CZA0FOpU.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"subscribeAnim-BqBgmjD4.js\",\"boxOpacity-CxJqpgqJ.js\"],\"panelExportProcess-CtsabYhm.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"type-workspace-U7I6uKdn.js\",\"boxOpacity-9sGMQ6qd.js\"],\"react-Cd33N7Zq.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"rolldown-runtime-BDP3x1s3.js\":[],\"sharedVault-mMOG3PsG.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"speedSample-BXiYiZ4E.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"subscribeAnim-BqBgmjD4.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"react-Cd33N7Zq.js\",\"contracts-C4-BRCqz.js\"],\"type-workspace-U7I6uKdn.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"contracts-C4-BRCqz.js\",\"assetStore-4qfBrYoN.js\"],\"v2AssetGen-DLqHswHt.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"view-D6ZHgOjJ.js\",\"sharedVault-mMOG3PsG.js\",\"vectorizeClient-CSsuV7Ew.js\",\"vectorizeClient-BGAa1YU-.js\",\"boxOpacity-9sGMQ6qd.js\"],\"vectorizeClient-BGAa1YU-.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"vectorizeClient-CSsuV7Ew.js\"],\"vectorizeClient-CSsuV7Ew.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"videoSource-_Ta7C4qY.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"liveOverlaysCanvas-DfCRS-HH.js\",\"view-D6ZHgOjJ.js\",\"boxOpacity-CxJqpgqJ.js\",\"overlaysCanvas-CZA0FOpU.js\",\"bind-CkWXB-UM.js\",\"speedSample-BXiYiZ4E.js\",\"sharedVault-mMOG3PsG.js\"],\"view-D6ZHgOjJ.js\":[\"rolldown-runtime-BDP3x1s3.js\",\"react-Cd33N7Zq.js\",\"editorAdds-CY8-DaYg.js\",\"editorTimelines-BHnIf5H0.js\",\"contracts-C4-BRCqz.js\",\"subscribeAnim-BqBgmjD4.js\",\"assetStore-4qfBrYoN.js\",\"type-workspace-U7I6uKdn.js\"],\"wasm_vtracer_bg-BPZSQuDe.js\":[\"rolldown-runtime-BDP3x1s3.js\"],\"wavesurfer.esm-B4xOEftU.js\":[\"rolldown-runtime-BDP3x1s3.js\"]},dir=\"code/\",factories=Object.create(null),cache=Object.create(null),loading=Object.create(null);\nfunction name(id){return String(id).replace(/^\\.\\//,\"\")}\nfunction require(id){var key=name(id);if(cache[key])return cache[key].exports;var factory=factories[key];if(!factory)throw new Error(key+\" is not loaded\");var module={exports:{}};cache[key]=module;factory.call(undefined,module.exports,require,module,load);return module.exports}\nfunction needed(key,into){if(into.indexOf(key)<0){into.push(key);(graph[key]||[]).forEach(function(next){needed(next,into)})}return into}\nfunction get(key){if(factories[key])return Promise.resolve();return loading[key]||(loading[key]=StillMade.loadCode(dir+key).then(function(){if(!factories[key])throw new Error(dir+key+\" did not define its module\")},function(error){delete loading[key];throw error}))}\nfunction load(id){return Promise.all(needed(name(id),[]).map(get))}\nObject.defineProperty(globalThis,\"__stillmadeCode_bfe7de0f1190\",{value:Object.freeze({define:function(key,factory){if(!factories[key])factories[key]=factory}})});\nload(\"view-BfB00tVc.js\").then(function(){require(\"view-BfB00tVc.js\")}).catch(function(error){var note=document.createElement(\"p\");note.setAttribute(\"role\",\"alert\");note.style.cssText=\"margin:16px;color:var(--fg)\";note.textContent=\"This screen could not load: \"+(error&&error.message||error);(document.getElementById(\"root\")||document.body).appendChild(note);throw error})})();\n",
200
+ "runtime": "frame",
201
+ "files": {
202
+ "schemaVersion": 1,
203
+ "files": {
204
+ "code/MediaViewer-CkF9dtek.js": {
205
+ "sha256": "eac30aa758e33337a22be61e860b6131c1260f1bba75a02712f3e9a94bd7f806",
206
+ "bytes": 1053,
207
+ "mediaType": "text/javascript"
208
+ },
209
+ "code/MobileEditor-4328GdaI.js": {
210
+ "sha256": "6879f1f36e6ffd18b53e4d828ef6589aa5edfc4097f6ba5de00765650cadac42",
211
+ "bytes": 168629,
212
+ "mediaType": "text/javascript"
213
+ },
214
+ "code/PanelBlocksPanel-C9reO-vH.js": {
215
+ "sha256": "0efaea039fe4d7a9c2232864fdb29611f036f4257089353082fd0f803224bcf6",
216
+ "bytes": 316146,
217
+ "mediaType": "text/javascript"
218
+ },
219
+ "code/VideoEditor-BZqIpoNf.js": {
220
+ "sha256": "e0ee96e1d64c8c7e43ae43702ce733d35c7c49bb59041d38f6fbeb97c50bd577",
221
+ "bytes": 482065,
222
+ "mediaType": "text/javascript"
223
+ },
224
+ "code/_frame-empty_wasm_vtracer_wasm_vtracer_bg_wasm_url-C085yj93.js": {
225
+ "sha256": "3e40e2c88eb2e7e3c02279b36e87b6a5ce8f5d5b530ffb26f4d01247bb533c50",
226
+ "bytes": 269,
227
+ "mediaType": "text/javascript"
228
+ },
229
+ "code/assetStore-4qfBrYoN.js": {
230
+ "sha256": "6b4d8a0f74b3a79fd847747ecd5e9affd69a7a89884c620dce0b74f8c04bfe52",
231
+ "bytes": 1511,
232
+ "mediaType": "text/javascript"
233
+ },
234
+ "code/bind-CkWXB-UM.js": {
235
+ "sha256": "ae8c618bb3dd1d3732a6757574b8a8197a6c5e592ff508c8e8498fd5286be5cb",
236
+ "bytes": 349873,
237
+ "mediaType": "text/javascript"
238
+ },
239
+ "code/boxOpacity-9sGMQ6qd.js": {
240
+ "sha256": "d5e29d286668b102b28480985b55a62f7025fd2e0f2c7dea9405c8ca389b72e8",
241
+ "bytes": 350824,
242
+ "mediaType": "text/javascript"
243
+ },
244
+ "code/boxOpacity-CxJqpgqJ.js": {
245
+ "sha256": "c91ac644c4fc9d8febe12384631bfaeca3bd6a3b1efecc2b83d8e0ec39cff62b",
246
+ "bytes": 14167,
247
+ "mediaType": "text/javascript"
248
+ },
249
+ "code/browser-CGfns6yT.js": {
250
+ "sha256": "d2f9a02fe41bee7e21eaeae2e4b447ada01cb798ab331e854ddd653a70b7973f",
251
+ "bytes": 12691,
252
+ "mediaType": "text/javascript"
253
+ },
254
+ "code/contracts-C4-BRCqz.js": {
255
+ "sha256": "592eb96757265e95b566a5c8d9e4dd63cf338e9b4b9c211d141757a3b6fe1d25",
256
+ "bytes": 63845,
257
+ "mediaType": "text/javascript"
258
+ },
259
+ "code/editorAdds-CY8-DaYg.js": {
260
+ "sha256": "fade9743324565452281dc83233b02217db439dfb6413e02c33c05ba11b8c1e8",
261
+ "bytes": 194975,
262
+ "mediaType": "text/javascript"
263
+ },
264
+ "code/editorTimelines-BHnIf5H0.js": {
265
+ "sha256": "52c10f896a6780f7569615c092fe5d9c148df66cd205b9c4d1390a0af6a25397",
266
+ "bytes": 69058,
267
+ "mediaType": "text/javascript"
268
+ },
269
+ "code/exportRender-BX-s25gI.js": {
270
+ "sha256": "52d04c4072fddbf83977705fb376cfa2cef9984003886b30a523bbac87826473",
271
+ "bytes": 47153,
272
+ "mediaType": "text/javascript"
273
+ },
274
+ "code/exportRender-CIHSH4Lo.js": {
275
+ "sha256": "d20409213361c326f9f5e3b39bc4345f2ee7689dcf2a1a45f12b81d10e5d625c",
276
+ "bytes": 348,
277
+ "mediaType": "text/javascript"
278
+ },
279
+ "code/favorites-BjGs2PS6.js": {
280
+ "sha256": "a353dac5bf2ce1a73ceb8835ba9c5ab72b5c5d9b6a403a9dc3941af42ec2a467",
281
+ "bytes": 182,
282
+ "mediaType": "text/javascript"
283
+ },
284
+ "code/frameExtract-CpwOQS0p.js": {
285
+ "sha256": "cfa13d283c3abc4bcfe9fef0862a0fbd90c1b0bb7e94c60b5410f4ed88fd1d20",
286
+ "bytes": 1336,
287
+ "mediaType": "text/javascript"
288
+ },
289
+ "code/imagetracer_v1.2.6-BBGerUPj.js": {
290
+ "sha256": "d009214481dc2dd2cf4e15acfcc2b0f88991d70d68265780f22e6fd1b1e15ba3",
291
+ "bytes": 19877,
292
+ "mediaType": "text/javascript"
293
+ },
294
+ "code/lamejs-Dix5aA0E.js": {
295
+ "sha256": "904e6e081c287058228e1f6f63853018dab0cfbe820b3fd1774e81107c06979d",
296
+ "bytes": 163273,
297
+ "mediaType": "text/javascript"
298
+ },
299
+ "code/liveOverlaysCanvas-DfCRS-HH.js": {
300
+ "sha256": "32b8d87fb1723ef9608f713f96c125348f444fa29ce483d2e904ef913282e1cd",
301
+ "bytes": 34146,
302
+ "mediaType": "text/javascript"
303
+ },
304
+ "code/media-player-B5xtn5y5.js": {
305
+ "sha256": "0b769c5f5ce2b164d396f8baeea71fee5160164e2dd01ed4774a48ea44d5f616",
306
+ "bytes": 19849,
307
+ "mediaType": "text/javascript"
308
+ },
309
+ "code/nativeRender-CFoK_eQg.js": {
310
+ "sha256": "cee4c4e812a36119214d8303a5ac0689157c4225c109ed5ba48bbdcfeb095a11",
311
+ "bytes": 2601,
312
+ "mediaType": "text/javascript"
313
+ },
314
+ "code/overlaysCanvas-CZA0FOpU.js": {
315
+ "sha256": "dbced64f8d142c0cdcd632ac4483ac702cd0efaf9815cbd34ec06e7199f3d96c",
316
+ "bytes": 11939,
317
+ "mediaType": "text/javascript"
318
+ },
319
+ "code/panelExportProcess-CtsabYhm.js": {
320
+ "sha256": "3ce40f40a4320178b260a7d9b2382003512d1ba1ce4b0cef5f8ee2f70b3b71e9",
321
+ "bytes": 4590,
322
+ "mediaType": "text/javascript"
323
+ },
324
+ "code/react-Cd33N7Zq.js": {
325
+ "sha256": "dc117c28ee56e749811ab506aaff7f557cf02fc5b3c442870d0f77290088841d",
326
+ "bytes": 6674,
327
+ "mediaType": "text/javascript"
328
+ },
329
+ "code/rolldown-runtime-BDP3x1s3.js": {
330
+ "sha256": "2b1ef09b125440faa4d23ca16dc11c30ab4f9e3c3cb853c09f231bb2bba6e0c4",
331
+ "bytes": 1189,
332
+ "mediaType": "text/javascript"
333
+ },
334
+ "code/sharedVault-mMOG3PsG.js": {
335
+ "sha256": "132fed136a9462896c81b15f33e2d17c735f1405b2ba2d0616ecf67978850d93",
336
+ "bytes": 3026,
337
+ "mediaType": "text/javascript"
338
+ },
339
+ "code/speedSample-BXiYiZ4E.js": {
340
+ "sha256": "a2a79e6ee7fb228f41fe88c1c0d607dbb751441383dafad696c613dc4de4a722",
341
+ "bytes": 1474,
342
+ "mediaType": "text/javascript"
343
+ },
344
+ "code/subscribeAnim-BqBgmjD4.js": {
345
+ "sha256": "5c9bb6e7a680d3ea915cf0303615d5826c46075883e2fe1419020d7f3737a9f2",
346
+ "bytes": 3786,
347
+ "mediaType": "text/javascript"
348
+ },
349
+ "code/type-workspace-U7I6uKdn.js": {
350
+ "sha256": "c92be71bd8dedcf00bae589ede879e2e997cfdc2b4243ab27e5eb0ebd3d5a801",
351
+ "bytes": 260765,
352
+ "mediaType": "text/javascript"
353
+ },
354
+ "code/v2AssetGen-DLqHswHt.js": {
355
+ "sha256": "01b59599e773423ca9a1e532de58588e4fcc1a9b08cfb9fb0b4d97cdd6c7ab89",
356
+ "bytes": 3973,
357
+ "mediaType": "text/javascript"
358
+ },
359
+ "code/vectorizeClient-BGAa1YU-.js": {
360
+ "sha256": "6c5d15c18ed47437dba544170365396de0f8a45673090e80978b41d837fd941f",
361
+ "bytes": 442,
362
+ "mediaType": "text/javascript"
363
+ },
364
+ "code/vectorizeClient-CSsuV7Ew.js": {
365
+ "sha256": "37fb517aad0735abc6623695531c80ce8bb93493db5958e3ffb577fbb0413238",
366
+ "bytes": 5278,
367
+ "mediaType": "text/javascript"
368
+ },
369
+ "code/videoSource-_Ta7C4qY.js": {
370
+ "sha256": "7c065246e96ba90174f982dd054bcec8adc859ee3aa47f9897d8471e7d1b998f",
371
+ "bytes": 193830,
372
+ "mediaType": "text/javascript"
373
+ },
374
+ "code/view-BfB00tVc.js": {
375
+ "sha256": "3b9fba1385b889bee750d47e6d15db4dbdc32de4a38ed27c910e1fefe88a09c7",
376
+ "bytes": 206,
377
+ "mediaType": "text/javascript"
378
+ },
379
+ "code/view-D6ZHgOjJ.js": {
380
+ "sha256": "ed493c8af9d76d98f57a3cc2ff7bd4185800496fa06a20e68925a88ec5a1c4e0",
381
+ "bytes": 273199,
382
+ "mediaType": "text/javascript"
383
+ },
384
+ "code/wasm_vtracer_bg-BPZSQuDe.js": {
385
+ "sha256": "caaedb09a5fada258aa4cf45828fd87e222ab5317b82d52adbe56cee580d6149",
386
+ "bytes": 6530,
387
+ "mediaType": "text/javascript"
388
+ },
389
+ "code/wavesurfer.esm-B4xOEftU.js": {
390
+ "sha256": "5b20537b49d64ca4af8985c25d4013416eff880aa5fe3b967ff69cfd682389fb",
391
+ "bytes": 39687,
392
+ "mediaType": "text/javascript"
393
+ }
394
+ }
395
+ }
396
+ },
397
+ "tests": [
398
+ {
399
+ "name": "Opens with the narration, pictures and clips of earlier Steps",
400
+ "input": {
401
+ "make": "use",
402
+ "media": [
403
+ {
404
+ "assetId": "example-photo-1.jpg",
405
+ "versionId": "example-photo-1.jpg-v1",
406
+ "url": "/api/media/example/photo-1.jpg",
407
+ "kind": "image",
408
+ "mimeType": "image/jpeg",
409
+ "width": 1600,
410
+ "height": 1200
411
+ },
412
+ {
413
+ "assetId": "example-photo-3.jpg",
414
+ "versionId": "example-photo-3.jpg-v1",
415
+ "url": "/api/media/example/photo-3.jpg",
416
+ "kind": "image",
417
+ "mimeType": "image/jpeg",
418
+ "width": 1600,
419
+ "height": 1200
420
+ },
421
+ {
422
+ "assetId": "example-clip.mp4",
423
+ "versionId": "example-clip.mp4-v1",
424
+ "url": "/api/media/example/clip.mp4",
425
+ "kind": "video",
426
+ "mimeType": "video/mp4",
427
+ "width": 640,
428
+ "height": 360,
429
+ "duration": 2
430
+ }
431
+ ],
432
+ "narrationAudio": {
433
+ "assetId": "example-kitchen.wav",
434
+ "versionId": "example-kitchen.wav-v1",
435
+ "url": "/api/media/example/kitchen.wav",
436
+ "kind": "audio",
437
+ "mimeType": "audio/wav",
438
+ "duration": 2
439
+ },
440
+ "narrationTiming": {
441
+ "schemaVersion": 1,
442
+ "text": "A small idea.",
443
+ "words": [
444
+ {
445
+ "text": "A",
446
+ "start": 0,
447
+ "end": 0.2
448
+ },
449
+ {
450
+ "text": "small",
451
+ "start": 0.2,
452
+ "end": 0.6
453
+ },
454
+ {
455
+ "text": "idea.",
456
+ "start": 0.6,
457
+ "end": 1.1
458
+ }
459
+ ]
460
+ },
461
+ "narrationScript": {
462
+ "schemaVersion": 1,
463
+ "text": "A small idea."
464
+ }
465
+ },
466
+ "expected": {
467
+ "result": null
468
+ }
469
+ },
470
+ {
471
+ "name": "Gives the Editor document to the next Step",
472
+ "input": {
473
+ "make": "use",
474
+ "editor": {
475
+ "schemaVersion": 1,
476
+ "activeTimelineId": "timeline-1",
477
+ "tracks": [
478
+ {
479
+ "id": "video-1",
480
+ "type": "video"
481
+ }
482
+ ],
483
+ "clips": [
484
+ {
485
+ "id": "clip-1",
486
+ "trackId": "video-1",
487
+ "url": "https://media.example/picture-1.png",
488
+ "start": 0,
489
+ "duration": 4
490
+ }
491
+ ],
492
+ "audioItems": [],
493
+ "captions": [],
494
+ "textOverlays": [],
495
+ "effects": []
496
+ }
497
+ },
498
+ "expected": {
499
+ "editor": {
500
+ "schemaVersion": 1,
501
+ "activeTimelineId": "timeline-1",
502
+ "tracks": [
503
+ {
504
+ "id": "video-1",
505
+ "type": "video"
506
+ }
507
+ ],
508
+ "clips": [
509
+ {
510
+ "id": "clip-1",
511
+ "trackId": "video-1",
512
+ "url": "https://media.example/picture-1.png",
513
+ "start": 0,
514
+ "duration": 4
515
+ }
516
+ ],
517
+ "audioItems": [],
518
+ "captions": [],
519
+ "textOverlays": [],
520
+ "effects": []
521
+ },
522
+ "result": null
523
+ }
524
+ },
525
+ {
526
+ "name": "Makes a picture that keeps its reference",
527
+ "input": {
528
+ "make": "image",
529
+ "editor": {
530
+ "schemaVersion": 1,
531
+ "activeTimelineId": "timeline-1",
532
+ "tracks": [
533
+ {
534
+ "id": "video-1",
535
+ "type": "video"
536
+ }
537
+ ],
538
+ "clips": [
539
+ {
540
+ "id": "clip-1",
541
+ "trackId": "video-1",
542
+ "url": "https://media.example/picture-1.png",
543
+ "start": 0,
544
+ "duration": 4
545
+ }
546
+ ],
547
+ "audioItems": [],
548
+ "captions": [],
549
+ "textOverlays": [],
550
+ "effects": []
551
+ },
552
+ "request": {
553
+ "model": "gpt-image-2",
554
+ "readablePrompt": "A title card at dusk",
555
+ "aspectRatio": "16:9",
556
+ "quality": "low",
557
+ "resolution": "1K"
558
+ },
559
+ "references": [
560
+ {
561
+ "kind": "image",
562
+ "assetId": "picture-1",
563
+ "versionId": "picture-1-v1",
564
+ "url": "https://media.example/picture-1.png",
565
+ "mimeType": "image/png",
566
+ "width": 1024,
567
+ "height": 1024,
568
+ "bytes": 200000
569
+ }
570
+ ]
571
+ },
572
+ "mocks": {
573
+ "hosted": {
574
+ "image.generate": {
575
+ "kind": "image",
576
+ "assetId": "picture-2",
577
+ "versionId": "picture-2-v1",
578
+ "url": "https://media.example/picture-2.png",
579
+ "mimeType": "image/png",
580
+ "width": 1024,
581
+ "height": 1024,
582
+ "bytes": 200000
583
+ }
584
+ }
585
+ },
586
+ "expected": {
587
+ "editor": {
588
+ "schemaVersion": 1,
589
+ "activeTimelineId": "timeline-1",
590
+ "tracks": [
591
+ {
592
+ "id": "video-1",
593
+ "type": "video"
594
+ }
595
+ ],
596
+ "clips": [
597
+ {
598
+ "id": "clip-1",
599
+ "trackId": "video-1",
600
+ "url": "https://media.example/picture-1.png",
601
+ "start": 0,
602
+ "duration": 4
603
+ }
604
+ ],
605
+ "audioItems": [],
606
+ "captions": [],
607
+ "textOverlays": [],
608
+ "effects": []
609
+ },
610
+ "made": [
611
+ {
612
+ "kind": "image",
613
+ "assetId": "picture-2",
614
+ "versionId": "picture-2-v1",
615
+ "url": "https://media.example/picture-2.png",
616
+ "mimeType": "image/png",
617
+ "width": 1024,
618
+ "height": 1024,
619
+ "bytes": 200000
620
+ }
621
+ ],
622
+ "result": {
623
+ "kind": "image",
624
+ "assetId": "picture-2",
625
+ "versionId": "picture-2-v1",
626
+ "url": "https://media.example/picture-2.png",
627
+ "mimeType": "image/png",
628
+ "width": 1024,
629
+ "height": 1024,
630
+ "bytes": 200000
631
+ }
632
+ }
633
+ },
634
+ {
635
+ "name": "Finds the words of the narration",
636
+ "input": {
637
+ "make": "transcribe",
638
+ "editor": {
639
+ "schemaVersion": 1,
640
+ "activeTimelineId": "timeline-1",
641
+ "tracks": [
642
+ {
643
+ "id": "video-1",
644
+ "type": "video"
645
+ }
646
+ ],
647
+ "clips": [
648
+ {
649
+ "id": "clip-1",
650
+ "trackId": "video-1",
651
+ "url": "https://media.example/picture-1.png",
652
+ "start": 0,
653
+ "duration": 4
654
+ }
655
+ ],
656
+ "audioItems": [],
657
+ "captions": [],
658
+ "textOverlays": [],
659
+ "effects": []
660
+ },
661
+ "audio": {
662
+ "kind": "audio",
663
+ "assetId": "voice-1",
664
+ "versionId": "voice-1-v1",
665
+ "url": "https://media.example/voice-1.wav",
666
+ "mimeType": "audio/wav",
667
+ "duration": 1.1,
668
+ "bytes": 52844,
669
+ "sampleRate": 24000,
670
+ "channels": 1
671
+ }
672
+ },
673
+ "mocks": {
674
+ "hosted": {
675
+ "audio.transcribe": {
676
+ "schemaVersion": 1,
677
+ "text": "A small idea.",
678
+ "words": [
679
+ {
680
+ "text": "A",
681
+ "start": 0,
682
+ "end": 0.2
683
+ },
684
+ {
685
+ "text": "small",
686
+ "start": 0.2,
687
+ "end": 0.6
688
+ },
689
+ {
690
+ "text": "idea.",
691
+ "start": 0.6,
692
+ "end": 1.1
693
+ }
694
+ ]
695
+ }
696
+ }
697
+ },
698
+ "expected": {
699
+ "editor": {
700
+ "schemaVersion": 1,
701
+ "activeTimelineId": "timeline-1",
702
+ "tracks": [
703
+ {
704
+ "id": "video-1",
705
+ "type": "video"
706
+ }
707
+ ],
708
+ "clips": [
709
+ {
710
+ "id": "clip-1",
711
+ "trackId": "video-1",
712
+ "url": "https://media.example/picture-1.png",
713
+ "start": 0,
714
+ "duration": 4
715
+ }
716
+ ],
717
+ "audioItems": [],
718
+ "captions": [],
719
+ "textOverlays": [],
720
+ "effects": []
721
+ },
722
+ "result": {
723
+ "text": "A small idea.",
724
+ "words": [
725
+ {
726
+ "word": "A",
727
+ "text": "A",
728
+ "start": 0,
729
+ "end": 0.2
730
+ },
731
+ {
732
+ "word": "small",
733
+ "text": "small",
734
+ "start": 0.2,
735
+ "end": 0.6
736
+ },
737
+ {
738
+ "word": "idea.",
739
+ "text": "idea.",
740
+ "start": 0.6,
741
+ "end": 1.1
742
+ }
743
+ ]
744
+ }
745
+ }
746
+ }
747
+ ],
748
+ "moduleSource": {
749
+ "main.js": "//#region blocks/native/sm.editor/frame/main.js\nconst fail = (message) => {\n\tthrow Error(message);\n};\nconst plain = (value) => !!value && typeof value === \"object\" && !Array.isArray(value);\nconst isMedia = (value, kind) => plain(value) && typeof value.url === \"string\" && typeof value.assetId === \"string\" && (!kind || value.kind === kind);\nconst dataUrlBytes = (dataUrl) => {\n\tconst match = /^data:([a-z]+\\/[a-z0-9.+-]+)(?:;[^,;]*)*;base64,(.+)$/i.exec(String(dataUrl || \"\"));\n\tif (!match) fail(\"Choose a file to save.\");\n\tconst text = atob(match[2]), bytes = new Uint8Array(text.length);\n\tfor (let index = 0; index < text.length; index++) bytes[index] = text.charCodeAt(index);\n\treturn {\n\t\tmediaType: match[1].toLowerCase(),\n\t\tbuffer: bytes.buffer\n\t};\n};\nconst EXTENSIONS = {\n\t\"image/png\": \"png\",\n\t\"image/jpeg\": \"jpg\",\n\t\"image/webp\": \"webp\",\n\t\"image/gif\": \"gif\",\n\t\"video/mp4\": \"mp4\",\n\t\"video/webm\": \"webm\",\n\t\"video/quicktime\": \"mov\",\n\t\"audio/mpeg\": \"mp3\",\n\t\"audio/wav\": \"wav\",\n\t\"audio/x-wav\": \"wav\",\n\t\"audio/mp4\": \"m4a\",\n\t\"audio/ogg\": \"ogg\",\n\t\"audio/webm\": \"webm\"\n};\nasync function run(input, stillmade) {\n\tconst make = typeof input.make === \"string\" ? input.make : \"use\";\n\tconst request = plain(input.request) ? input.request : {};\n\tconst references = (Array.isArray(input.references) ? input.references : []).filter((value) => isMedia(value, \"image\"));\n\tconst editor = plain(input.editor) ? {\n\t\t...input.editor,\n\t\tschemaVersion: input.editor.schemaVersion ?? 1\n\t} : null;\n\tconst done = (result, made = []) => ({\n\t\t...editor ? { editor } : {},\n\t\t...made.length ? { made } : {},\n\t\tresult: result === void 0 ? null : result\n\t});\n\tswitch (make) {\n\t\tcase \"use\": return done(null);\n\t\tcase \"image\": {\n\t\t\tif (typeof request.readablePrompt !== \"string\" || !request.readablePrompt.trim()) fail(\"Write what the picture shows first.\");\n\t\t\tstillmade.progress(.2, \"Making the picture\");\n\t\t\tconst settings = Object.fromEntries(Object.entries({\n\t\t\t\tmodel: request.model,\n\t\t\t\taspectRatio: request.aspectRatio,\n\t\t\t\tquality: request.quality,\n\t\t\t\tresolution: request.resolution\n\t\t\t}).filter(([, value]) => typeof value === \"string\" && value));\n\t\t\tconst made = await stillmade.hosted.run(\"image.generate\", {\n\t\t\t\tprompt: request.readablePrompt,\n\t\t\t\t...references.length ? { references: references.slice(0, 8) } : {},\n\t\t\t\t...Object.keys(settings).length ? { settings } : {}\n\t\t\t});\n\t\t\treturn done(made, [made]);\n\t\t}\n\t\tcase \"video\": {\n\t\t\tstillmade.progress(.1, \"Making the clip\");\n\t\t\tconst settings = Object.fromEntries(Object.entries({\n\t\t\t\tmodel: request.model,\n\t\t\t\taspectRatio: request.aspectRatio,\n\t\t\t\tquality: request.quality,\n\t\t\t\tresolution: request.resolution,\n\t\t\t\tduration: request.duration,\n\t\t\t\tseed: request.seed,\n\t\t\t\tgenerateAudio: request.generateAudio\n\t\t\t}).filter(([, value]) => value !== void 0 && value !== null && value !== \"\"));\n\t\t\tconst made = await stillmade.hosted.run(\"video.generate\", {\n\t\t\t\tprompt: request.readablePrompt || \"\",\n\t\t\t\t...isMedia(input.firstFrame, \"image\") ? { firstFrame: input.firstFrame } : {},\n\t\t\t\t...isMedia(input.lastFrame, \"image\") ? { lastFrame: input.lastFrame } : {},\n\t\t\t\t...request.negativePrompt ? { negativePrompt: request.negativePrompt } : {},\n\t\t\t\t...Object.keys(settings).length ? { settings } : {}\n\t\t\t});\n\t\t\treturn done(made, [made]);\n\t\t}\n\t\tcase \"transcribe\": {\n\t\t\tif (!isMedia(input.audio)) fail(\"Choose the sound to transcribe.\");\n\t\t\tstillmade.progress(.2, \"Finding the words\");\n\t\t\tconst timing = await stillmade.hosted.run(\"audio.transcribe\", { audio: input.audio });\n\t\t\tconst words = (Array.isArray(timing?.words) ? timing.words : []).map((word) => ({\n\t\t\t\tword: word.text ?? word.word,\n\t\t\t\ttext: word.text ?? word.word,\n\t\t\t\tstart: word.start,\n\t\t\t\tend: word.end\n\t\t\t}));\n\t\t\treturn done({\n\t\t\t\ttext: String(timing?.text || words.map((word) => word.text).join(\" \")),\n\t\t\t\twords\n\t\t\t});\n\t\t}\n\t\tcase \"upload\": {\n\t\t\tconst { mediaType, buffer } = dataUrlBytes(input.dataUrl);\n\t\t\tconst extension = EXTENSIONS[mediaType] || fail(\"Save a picture, a clip or a sound file.\");\n\t\t\tconst name = String(input.key || `upload.${extension}`).replace(/[^\\w.-]+/g, \"-\").slice(0, 120) || `upload.${extension}`;\n\t\t\tconst saved = await stillmade.files.write(buffer, {\n\t\t\t\tmediaType,\n\t\t\t\tname: /\\.[a-z0-9]{2,5}$/i.test(name) ? name : `${name}.${extension}`\n\t\t\t});\n\t\t\treturn done(saved, [saved]);\n\t\t}\n\t\tcase \"export\": {\n\t\t\tconst kind = request.kind === \"audio\" ? \"audio\" : \"video\";\n\t\t\tstillmade.progress(.05, kind === \"audio\" ? \"Mixing the sound\" : \"Rendering the video\");\n\t\t\tconst { asset } = await stillmade.actions.run({\n\t\t\t\tschemaVersion: 1,\n\t\t\t\tkind: \"stillmade.media-action\",\n\t\t\t\toperation: \"final.render\",\n\t\t\t\tresultPort: null,\n\t\t\t\trequest: {\n\t\t\t\t\tschemaVersion: 1,\n\t\t\t\t\tkind,\n\t\t\t\t\ttimeline: request.timeline,\n\t\t\t\t\tsettings: request.settings,\n\t\t\t\t\tsource: plain(request.source) ? request.source : {\n\t\t\t\t\t\tid: \"sm.editor\",\n\t\t\t\t\t\tversion: \"3.0.0\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\tconst kept = {\n\t\t\t\tkind: asset.kind,\n\t\t\t\tassetId: asset.assetId,\n\t\t\t\tversionId: asset.versionId,\n\t\t\t\turl: asset.url,\n\t\t\t\t...asset.mimeType ? { mimeType: asset.mimeType } : {},\n\t\t\t\t...Number(asset.duration) > 0 ? { duration: Number(asset.duration) } : {}\n\t\t\t};\n\t\t\treturn {\n\t\t\t\t...done(kept, [kept]),\n\t\t\t\texport: kept\n\t\t\t};\n\t\t}\n\t\tcase \"stock-status\": return done({ available: true });\n\t\tcase \"stock-grid\": {\n\t\t\tconst { results } = await stillmade.actions.run({\n\t\t\t\tschemaVersion: 1,\n\t\t\t\tkind: \"stillmade.media-action\",\n\t\t\t\toperation: \"stock.search\",\n\t\t\t\tresultPort: null,\n\t\t\t\trequest: {\n\t\t\t\t\tquery: String(request.query || request.q || \"\").slice(0, 500) || \"scene\",\n\t\t\t\t\ttype: request.type === \"video\" ? \"video\" : \"photo\"\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn done(results || []);\n\t\t}\n\t\tcase \"storage-get\": return done(await stillmade.storage.get(String(input.key || \"\")));\n\t\tcase \"storage-set\":\n\t\t\tawait stillmade.storage.set(String(input.key || \"\"), input.value ?? null);\n\t\t\treturn done(true);\n\t}\n\tfail(`${make} is not something the Editor Block does yet.`);\n}\n\n//#endregion\nexport { run as default };"
750
+ }
751
+ };