@3driseai/3drise-core 1.0.20 → 1.0.22

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 (185) hide show
  1. package/dist/autoAnimate/composer.d.ts +6 -0
  2. package/dist/autoAnimate/composer.d.ts.map +1 -1
  3. package/dist/autoAnimate/composer.js +4 -3
  4. package/dist/autoAnimate/describeScene.js +1 -1
  5. package/dist/catalog/catalogInfo.d.ts +22 -0
  6. package/dist/catalog/catalogInfo.d.ts.map +1 -0
  7. package/dist/catalog/catalogInfo.js +204 -0
  8. package/dist/catalog/index.d.ts +3 -0
  9. package/dist/catalog/index.d.ts.map +1 -0
  10. package/dist/catalog/index.js +2 -0
  11. package/dist/catalog/objectCatalog.d.ts +33 -0
  12. package/dist/catalog/objectCatalog.d.ts.map +1 -0
  13. package/dist/catalog/objectCatalog.js +99 -0
  14. package/dist/data/allMaterials.js +2 -2
  15. package/dist/data/environmentDefaults.d.ts +10 -0
  16. package/dist/data/environmentDefaults.d.ts.map +1 -0
  17. package/dist/data/environmentDefaults.js +126 -0
  18. package/dist/data/gridsDefaults.d.ts +2 -0
  19. package/dist/data/gridsDefaults.d.ts.map +1 -1
  20. package/dist/data/gridsDefaults.js +186 -0
  21. package/dist/data/index.d.ts +4 -0
  22. package/dist/data/index.d.ts.map +1 -1
  23. package/dist/data/index.js +4 -0
  24. package/dist/data/landDefaults.d.ts +26 -0
  25. package/dist/data/landDefaults.d.ts.map +1 -0
  26. package/dist/data/landDefaults.js +76 -0
  27. package/dist/data/spaceDefaults.d.ts +10 -0
  28. package/dist/data/spaceDefaults.d.ts.map +1 -0
  29. package/dist/data/spaceDefaults.js +46 -0
  30. package/dist/data/textDefaults.d.ts +16 -0
  31. package/dist/data/textDefaults.d.ts.map +1 -0
  32. package/dist/data/textDefaults.js +199 -0
  33. package/dist/environment/oceanConfigs.d.ts +5 -4
  34. package/dist/environment/oceanConfigs.d.ts.map +1 -1
  35. package/dist/environment/oceanConfigs.js +2 -2
  36. package/dist/environment/terrainConfigs.d.ts +78 -0
  37. package/dist/environment/terrainConfigs.d.ts.map +1 -1
  38. package/dist/environment/terrainConfigs.js +72 -2
  39. package/dist/index.d.ts +3 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +3 -1
  42. package/dist/properties/registry.d.ts +10 -1
  43. package/dist/properties/registry.d.ts.map +1 -1
  44. package/dist/properties/registry.js +60 -9
  45. package/dist/properties/tables/environmentProperties.d.ts +9 -0
  46. package/dist/properties/tables/environmentProperties.d.ts.map +1 -0
  47. package/dist/properties/tables/environmentProperties.js +127 -0
  48. package/dist/properties/tables/gridProperties.d.ts.map +1 -1
  49. package/dist/properties/tables/gridProperties.js +184 -82
  50. package/dist/properties/tables/index.d.ts +4 -1
  51. package/dist/properties/tables/index.d.ts.map +1 -1
  52. package/dist/properties/tables/index.js +4 -1
  53. package/dist/properties/tables/landProperties.d.ts +5 -0
  54. package/dist/properties/tables/landProperties.d.ts.map +1 -0
  55. package/dist/properties/tables/landProperties.js +24 -0
  56. package/dist/properties/tables/lightProperties.js +1 -1
  57. package/dist/properties/tables/materialBounds.js +1 -1
  58. package/dist/properties/tables/optionalProperties.d.ts +1 -2
  59. package/dist/properties/tables/optionalProperties.d.ts.map +1 -1
  60. package/dist/properties/tables/optionalProperties.js +21 -0
  61. package/dist/properties/tables/spaceProperties.d.ts +8 -0
  62. package/dist/properties/tables/spaceProperties.d.ts.map +1 -0
  63. package/dist/properties/tables/spaceProperties.js +42 -0
  64. package/dist/properties/tables/textProperties.d.ts +4 -0
  65. package/dist/properties/tables/textProperties.d.ts.map +1 -0
  66. package/dist/properties/tables/textProperties.js +361 -0
  67. package/dist/properties/types.d.ts +1 -1
  68. package/dist/properties/types.d.ts.map +1 -1
  69. package/dist/services/MaterialRegistry.d.ts.map +1 -1
  70. package/dist/services/MaterialRegistry.js +2 -0
  71. package/dist/services/SkySystemManager.d.ts +22 -0
  72. package/dist/services/SkySystemManager.d.ts.map +1 -1
  73. package/dist/services/SkySystemManager.js +50 -18
  74. package/dist/services/TextureResolver.d.ts.map +1 -1
  75. package/dist/services/TextureResolver.js +22 -0
  76. package/dist/services/index.d.ts +1 -1
  77. package/dist/services/index.d.ts.map +1 -1
  78. package/dist/services/index.js +1 -1
  79. package/dist/shaders/ApollonianGridMaterial.d.ts +70 -0
  80. package/dist/shaders/ApollonianGridMaterial.d.ts.map +1 -0
  81. package/dist/shaders/ApollonianGridMaterial.js +289 -0
  82. package/dist/shaders/ConformalGridMaterial.d.ts +91 -0
  83. package/dist/shaders/ConformalGridMaterial.d.ts.map +1 -0
  84. package/dist/shaders/ConformalGridMaterial.js +335 -0
  85. package/dist/shaders/HilbertGridMaterial.d.ts +79 -0
  86. package/dist/shaders/HilbertGridMaterial.d.ts.map +1 -0
  87. package/dist/shaders/HilbertGridMaterial.js +329 -0
  88. package/dist/shaders/LensingGridMaterial.d.ts +73 -0
  89. package/dist/shaders/LensingGridMaterial.d.ts.map +1 -0
  90. package/dist/shaders/LensingGridMaterial.js +253 -0
  91. package/dist/shaders/ParastichyGridMaterial.d.ts +76 -0
  92. package/dist/shaders/ParastichyGridMaterial.d.ts.map +1 -0
  93. package/dist/shaders/ParastichyGridMaterial.js +282 -0
  94. package/dist/shaders/PhyllotaxisGridMaterial.d.ts +85 -0
  95. package/dist/shaders/PhyllotaxisGridMaterial.d.ts.map +1 -0
  96. package/dist/shaders/PhyllotaxisGridMaterial.js +532 -0
  97. package/dist/shaders/SkyboxMaterial.js +3 -3
  98. package/dist/shaders/StarsMaterial.d.ts +5 -0
  99. package/dist/shaders/StarsMaterial.d.ts.map +1 -1
  100. package/dist/shaders/StarsMaterial.js +59 -57
  101. package/dist/shaders/TruchetGridMaterial.d.ts +76 -0
  102. package/dist/shaders/TruchetGridMaterial.d.ts.map +1 -0
  103. package/dist/shaders/TruchetGridMaterial.js +290 -0
  104. package/dist/shaders/index.d.ts +7 -0
  105. package/dist/shaders/index.d.ts.map +1 -1
  106. package/dist/shaders/index.js +7 -0
  107. package/dist/types/actions.d.ts +6 -5
  108. package/dist/types/actions.d.ts.map +1 -1
  109. package/dist/types/commands.d.ts +61 -0
  110. package/dist/types/commands.d.ts.map +1 -0
  111. package/dist/types/commands.js +1 -0
  112. package/dist/types/environment.d.ts +34 -1
  113. package/dist/types/environment.d.ts.map +1 -1
  114. package/dist/types/environment.js +15 -0
  115. package/dist/types/environmentObjects.d.ts +152 -0
  116. package/dist/types/environmentObjects.d.ts.map +1 -0
  117. package/dist/types/environmentObjects.js +10 -0
  118. package/dist/types/index.d.ts +5 -1
  119. package/dist/types/index.d.ts.map +1 -1
  120. package/dist/types/landObjects.d.ts +15 -0
  121. package/dist/types/landObjects.d.ts.map +1 -0
  122. package/dist/types/landObjects.js +1 -0
  123. package/dist/types/objectSettings.d.ts +187 -16
  124. package/dist/types/objectSettings.d.ts.map +1 -1
  125. package/dist/types/objectsConfigs.d.ts +0 -3
  126. package/dist/types/objectsConfigs.d.ts.map +1 -1
  127. package/dist/types/scene3d.d.ts +8 -5
  128. package/dist/types/scene3d.d.ts.map +1 -1
  129. package/dist/types/spaceObjects.d.ts +65 -0
  130. package/dist/types/spaceObjects.d.ts.map +1 -0
  131. package/dist/types/spaceObjects.js +11 -0
  132. package/dist/types/textObjects.d.ts +352 -0
  133. package/dist/types/textObjects.d.ts.map +1 -0
  134. package/dist/types/textObjects.js +14 -0
  135. package/dist/types/types.d.ts +3 -6
  136. package/dist/types/types.d.ts.map +1 -1
  137. package/dist/types/users.d.ts +3 -1
  138. package/dist/types/users.d.ts.map +1 -1
  139. package/dist/types/users.js +2 -0
  140. package/dist/ui/index.d.ts +5 -0
  141. package/dist/ui/index.d.ts.map +1 -0
  142. package/dist/ui/index.js +4 -0
  143. package/dist/ui/uiBindings.d.ts +45 -0
  144. package/dist/ui/uiBindings.d.ts.map +1 -0
  145. package/dist/ui/uiBindings.js +172 -0
  146. package/dist/ui/uiKeys.d.ts +14 -0
  147. package/dist/ui/uiKeys.d.ts.map +1 -0
  148. package/dist/ui/uiKeys.js +22 -0
  149. package/dist/ui/uiSanitize.d.ts +18 -0
  150. package/dist/ui/uiSanitize.d.ts.map +1 -0
  151. package/dist/ui/uiSanitize.js +251 -0
  152. package/dist/ui/uiSpec.d.ts +116 -0
  153. package/dist/ui/uiSpec.d.ts.map +1 -0
  154. package/dist/ui/uiSpec.js +173 -0
  155. package/dist/utils/environmentMigration.d.ts +3 -0
  156. package/dist/utils/environmentMigration.d.ts.map +1 -0
  157. package/dist/utils/environmentMigration.js +50 -0
  158. package/dist/utils/generativeAnimatable.d.ts +1 -1
  159. package/dist/utils/generativeAnimatable.d.ts.map +1 -1
  160. package/dist/utils/generativeAnimatable.js +12 -0
  161. package/dist/utils/getAnimatableProperties.d.ts +1 -1
  162. package/dist/utils/getAnimatableProperties.d.ts.map +1 -1
  163. package/dist/utils/getAnimatableProperties.js +1 -1
  164. package/dist/utils/gridMigration.d.ts +3 -0
  165. package/dist/utils/gridMigration.d.ts.map +1 -0
  166. package/dist/utils/gridMigration.js +34 -0
  167. package/dist/utils/index.d.ts +5 -0
  168. package/dist/utils/index.d.ts.map +1 -1
  169. package/dist/utils/index.js +5 -0
  170. package/dist/utils/objectEffects.d.ts +24 -2
  171. package/dist/utils/objectEffects.d.ts.map +1 -1
  172. package/dist/utils/objectEffects.js +29 -2
  173. package/dist/utils/objectMigration.d.ts +9 -0
  174. package/dist/utils/objectMigration.d.ts.map +1 -0
  175. package/dist/utils/objectMigration.js +12 -0
  176. package/dist/utils/skyPhase.d.ts +61 -0
  177. package/dist/utils/skyPhase.d.ts.map +1 -0
  178. package/dist/utils/skyPhase.js +93 -0
  179. package/dist/utils/spaceMigration.d.ts +3 -0
  180. package/dist/utils/spaceMigration.d.ts.map +1 -0
  181. package/dist/utils/spaceMigration.js +46 -0
  182. package/dist/utils/sunUtils.d.ts +26 -1
  183. package/dist/utils/sunUtils.d.ts.map +1 -1
  184. package/dist/utils/sunUtils.js +37 -6
  185. package/package.json +1 -1
@@ -0,0 +1,251 @@
1
+ // =============================================================================
2
+ // Sanitizer for UI layers
3
+ // =============================================================================
4
+ //
5
+ // Agent-written HTML/CSS runs on 3drise.ai and on other people's sites, so it is
6
+ // reduced to a closed, script-free subset before it is stored and again before it is
7
+ // rendered (the viewer never trusts what it is handed). Browser only: it needs
8
+ // DOMParser and constructable stylesheets.
9
+ //
10
+ // HTML: tag + attribute allow-lists. Scripts, styles, forms, frames, `on*`,
11
+ // `javascript:` links and non-https / non-image sources are removed.
12
+ // CSS: parsed by the browser (CSSOM), so escapes cannot hide anything; rules are
13
+ // re-serialised from what the browser understood. @import, @font-face and
14
+ // other at-rules are dropped (system fonts only); a rule with a `url()` that
15
+ // is not https: / data:image / #fragment is dropped. External https images are
16
+ // allowed — that is the one thing a layer can fetch.
17
+ //
18
+ const HTML_TAGS = new Set([
19
+ 'div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'button', 'a', 'label', 'input', 'select', 'option',
20
+ 'ul', 'ol', 'li', 'img', 'small', 'strong', 'em', 'b', 'i', 'u', 'br', 'hr', 'section', 'header', 'footer', 'nav',
21
+ 'article', 'aside', 'figure', 'figcaption', 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'dl', 'dt', 'dd', 'sub', 'sup', 'kbd', 'code', 'pre',
22
+ ]);
23
+ const SVG_TAGS = new Set([
24
+ 'svg', 'g', 'path', 'circle', 'ellipse', 'rect', 'line', 'polyline', 'polygon', 'defs', 'lineargradient', 'radialgradient',
25
+ 'stop', 'title', 'desc', 'text', 'tspan', 'clippath', 'mask',
26
+ ]);
27
+ /** Removed with their whole content (not unwrapped). */
28
+ const DROP_TAGS = new Set([
29
+ 'script', 'style', 'iframe', 'frame', 'frameset', 'object', 'embed', 'applet', 'link', 'meta', 'base', 'template', 'noscript',
30
+ 'form', 'textarea', 'video', 'audio', 'source', 'track', 'canvas', 'portal', 'dialog',
31
+ 'use', 'foreignobject', 'animate', 'animatetransform', 'animatemotion', 'set', 'image', 'filter', 'pattern', 'symbol', 'marker',
32
+ ]);
33
+ const GLOBAL_ATTRS = new Set([
34
+ 'class', 'id', 'style', 'title', 'role', 'hidden', 'lang', 'dir', 'draggable',
35
+ ]);
36
+ const HTML_ATTRS = new Set([
37
+ 'href', 'src', 'alt', 'width', 'height', 'type', 'min', 'max', 'step', 'value', 'placeholder', 'checked', 'disabled',
38
+ 'for', 'tabindex', 'target', 'rel', 'colspan', 'rowspan', 'selected', 'readonly', 'maxlength', 'inputmode',
39
+ ]);
40
+ const SVG_ATTRS = new Set([
41
+ 'viewbox', 'd', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'stroke-opacity', 'fill-opacity',
42
+ 'opacity', 'cx', 'cy', 'r', 'rx', 'ry', 'x', 'y', 'x1', 'y1', 'x2', 'y2', 'width', 'height', 'points', 'transform',
43
+ 'offset', 'stop-color', 'stop-opacity', 'gradientunits', 'gradienttransform', 'fill-rule', 'clip-rule',
44
+ 'preserveaspectratio', 'xmlns', 'font-size', 'text-anchor', 'dominant-baseline', 'stroke-dasharray', 'stroke-dashoffset',
45
+ 'clip-path', 'mask', 'vector-effect',
46
+ ]);
47
+ const INPUT_TYPES = new Set(['range', 'text', 'number', 'checkbox', 'radio', 'search', 'color']);
48
+ const IMG_DATA = /^data:image\/(png|jpe?g|gif|webp|avif|svg\+xml)[;,]/i;
49
+ const isHttps = (u) => /^https:\/\//i.test(u);
50
+ /** Normalised CSS text is checked, never the raw text: the browser has already resolved escapes. */
51
+ const BAD_CSS = /expression\s*\(|javascript:|vbscript:|-moz-binding|behavior\s*:|image-set\s*\(|\bsrc\s*\(|@import/i;
52
+ const URL_RE = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^)\s]*))\s*\)/gi;
53
+ function cssTextIsSafe(text) {
54
+ if (BAD_CSS.test(text))
55
+ return false;
56
+ URL_RE.lastIndex = 0;
57
+ let m;
58
+ while ((m = URL_RE.exec(text))) {
59
+ const u = (m[1] ?? m[2] ?? m[3] ?? '').trim();
60
+ if (!(isHttps(u) || IMG_DATA.test(u) || u.startsWith('#')))
61
+ return false;
62
+ }
63
+ return true;
64
+ }
65
+ function newSheet() {
66
+ try {
67
+ return new CSSStyleSheet();
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ const GROUPING = /^@(media|supports|container|layer)\b/i;
74
+ function serializeRule(rule, removed) {
75
+ const text = rule.cssText;
76
+ if (rule instanceof CSSStyleRule) {
77
+ const own = rule.style.cssText;
78
+ if (!cssTextIsSafe(own) || !cssTextIsSafe(rule.selectorText)) {
79
+ removed.push(`rule ${rule.selectorText}`);
80
+ return '';
81
+ }
82
+ const nested = Array.from(rule.cssRules ?? []).map(r => serializeRule(r, removed)).join('');
83
+ return `${rule.selectorText}{${own}${nested ? ';' + nested : ''}}`;
84
+ }
85
+ if (rule instanceof CSSKeyframesRule) {
86
+ const frames = Array.from(rule.cssRules).map(f => {
87
+ const k = f;
88
+ return cssTextIsSafe(k.style.cssText) ? `${k.keyText}{${k.style.cssText}}` : '';
89
+ }).join('');
90
+ return `@keyframes ${rule.name}{${frames}}`;
91
+ }
92
+ if (GROUPING.test(text) && 'cssRules' in rule) {
93
+ const header = text.slice(0, text.indexOf('{')).trim();
94
+ if (!cssTextIsSafe(header)) {
95
+ removed.push(header);
96
+ return '';
97
+ }
98
+ const inner = Array.from(rule.cssRules).map(r => serializeRule(r, removed)).join('');
99
+ return `${header}{${inner}}`;
100
+ }
101
+ removed.push(text.slice(0, 40).replace(/\s+/g, ' '));
102
+ return '';
103
+ }
104
+ /** Keep the rules the browser understood and that are safe; drop the rest. */
105
+ export function sanitizeUiCss(css) {
106
+ const removed = [];
107
+ const sheet = newSheet();
108
+ if (!sheet)
109
+ return { out: '', removed: ['this browser cannot sanitize CSS (no constructable stylesheets); the CSS was not applied'] };
110
+ try {
111
+ sheet.replaceSync(css ?? '');
112
+ }
113
+ catch {
114
+ return { out: '', removed: ['the CSS could not be parsed'] };
115
+ }
116
+ const out = Array.from(sheet.cssRules).map(r => serializeRule(r, removed)).join('\n');
117
+ if (/@import/i.test(css ?? ''))
118
+ removed.push('@import');
119
+ return { out, removed };
120
+ }
121
+ /** An inline `style="…"` value, normalised, or '' when unsafe. */
122
+ function sanitizeStyleAttr(value, removed) {
123
+ const sheet = newSheet();
124
+ if (!sheet) {
125
+ removed.push('style attribute');
126
+ return '';
127
+ }
128
+ try {
129
+ sheet.replaceSync(`x{${value}}`);
130
+ }
131
+ catch {
132
+ return '';
133
+ }
134
+ const rule = sheet.cssRules[0];
135
+ const text = rule instanceof CSSStyleRule ? rule.style.cssText : '';
136
+ if (!cssTextIsSafe(text)) {
137
+ removed.push('style attribute (unsafe url or function)');
138
+ return '';
139
+ }
140
+ return text;
141
+ }
142
+ const describe = (el) => `<${el.localName}>`;
143
+ function cleanElement(el, removed) {
144
+ const tag = el.localName.toLowerCase();
145
+ const isSvg = el.namespaceURI === 'http://www.w3.org/2000/svg';
146
+ const allowedAttrs = isSvg ? SVG_ATTRS : HTML_ATTRS;
147
+ for (const attr of Array.from(el.attributes)) {
148
+ const name = attr.name.toLowerCase();
149
+ const value = attr.value;
150
+ let keep = false;
151
+ if (name.startsWith('data-') || name.startsWith('aria-'))
152
+ keep = true;
153
+ else if (GLOBAL_ATTRS.has(name))
154
+ keep = true;
155
+ else if (allowedAttrs.has(name))
156
+ keep = true;
157
+ if (!keep) {
158
+ removed.push(`${describe(el)} ${name}`);
159
+ el.removeAttribute(attr.name);
160
+ continue;
161
+ }
162
+ if (name === 'style') {
163
+ const clean = sanitizeStyleAttr(value, removed);
164
+ if (clean)
165
+ el.setAttribute('style', clean);
166
+ else
167
+ el.removeAttribute('style');
168
+ }
169
+ else if (name === 'href') {
170
+ if (!(isHttps(value) || /^mailto:/i.test(value) || value.startsWith('#'))) {
171
+ removed.push(`${describe(el)} href`);
172
+ el.removeAttribute(attr.name);
173
+ }
174
+ }
175
+ else if (name === 'src') {
176
+ if (!(isHttps(value) || IMG_DATA.test(value))) {
177
+ removed.push(`${describe(el)} src`);
178
+ el.removeAttribute(attr.name);
179
+ }
180
+ }
181
+ else if (name === 'target') {
182
+ if (value !== '_blank')
183
+ el.removeAttribute(attr.name);
184
+ }
185
+ else if (name === 'tabindex') {
186
+ if (value !== '0' && value !== '-1')
187
+ el.removeAttribute(attr.name);
188
+ }
189
+ else if (name === 'type') {
190
+ if (tag === 'input' && !INPUT_TYPES.has(value.toLowerCase())) {
191
+ removed.push(`input type=${value}`);
192
+ el.setAttribute('type', 'text');
193
+ }
194
+ if (tag === 'button')
195
+ el.setAttribute('type', 'button');
196
+ }
197
+ else if (isSvg && (name === 'fill' || name === 'stroke' || name === 'clip-path' || name === 'mask')) {
198
+ if (!cssTextIsSafe(value)) {
199
+ removed.push(`${describe(el)} ${name}`);
200
+ el.removeAttribute(attr.name);
201
+ }
202
+ }
203
+ }
204
+ if (tag === 'button' && !el.hasAttribute('type'))
205
+ el.setAttribute('type', 'button');
206
+ if (tag === 'a') {
207
+ el.setAttribute('rel', 'noopener noreferrer');
208
+ }
209
+ }
210
+ function cleanChildren(parent, removed) {
211
+ for (const child of Array.from(parent.childNodes)) {
212
+ if (child.nodeType === 1) {
213
+ const el = child;
214
+ const tag = el.localName.toLowerCase();
215
+ const isSvg = el.namespaceURI === 'http://www.w3.org/2000/svg';
216
+ if (DROP_TAGS.has(tag)) {
217
+ removed.push(`<${tag}>`);
218
+ parent.removeChild(el);
219
+ continue;
220
+ }
221
+ if (!(isSvg ? SVG_TAGS : HTML_TAGS).has(tag)) {
222
+ // Unknown tag: keep its content, drop the tag.
223
+ removed.push(`<${tag}>`);
224
+ while (el.firstChild)
225
+ parent.insertBefore(el.firstChild, el);
226
+ parent.removeChild(el);
227
+ // The moved children are before `el`'s old position; re-clean the parent's new children.
228
+ cleanChildren(parent, removed);
229
+ return;
230
+ }
231
+ cleanElement(el, removed);
232
+ cleanChildren(el, removed);
233
+ }
234
+ else if (child.nodeType !== 3) {
235
+ parent.removeChild(child); // comments, processing instructions
236
+ }
237
+ }
238
+ }
239
+ /** Parse, clean and serialise a layer's HTML. Always returns something safe to assign to innerHTML. */
240
+ export function sanitizeUiHtml(html) {
241
+ const removed = [];
242
+ const doc = new DOMParser().parseFromString(`<!doctype html><body>${html ?? ''}`, 'text/html');
243
+ cleanChildren(doc.body, removed);
244
+ return { out: doc.body.innerHTML, removed: Array.from(new Set(removed)) };
245
+ }
246
+ /** Both fields of a layer. */
247
+ export function sanitizeUiLayer(layer) {
248
+ const h = sanitizeUiHtml(layer.html);
249
+ const c = sanitizeUiCss(layer.css);
250
+ return { layer: { ...layer, html: h.out, css: c.out }, removed: [...h.removed, ...c.removed] };
251
+ }
@@ -0,0 +1,116 @@
1
+ export interface UiLayer {
2
+ id: string;
3
+ /** Stable, code-facing name; unique per project (see uiKeys). */
4
+ key: string;
5
+ name: string;
6
+ description?: string;
7
+ html: string;
8
+ css: string;
9
+ visible: boolean;
10
+ /** Position in the project's list (junction table `layerOrder`). */
11
+ layerOrder?: number;
12
+ }
13
+ export type UiRefKind = 'action' | 'sequence' | 'clip';
14
+ /** `ui` addresses an element of the same layer by CSS id: `ui.toggle:#menu`. */
15
+ export type UiCommandKind = UiRefKind | 'ui';
16
+ /** Attributes that run commands. */
17
+ export declare const UI_TRIGGER_ATTRS: {
18
+ readonly click: "data-click";
19
+ readonly hoverIn: "data-hover-in";
20
+ readonly hoverOut: "data-hover-out";
21
+ };
22
+ export type UiTrigger = keyof typeof UI_TRIGGER_ATTRS;
23
+ export declare const UI_ATTR: {
24
+ readonly bind: "data-bind";
25
+ readonly state: "data-state";
26
+ readonly showWhen: "data-show-when";
27
+ readonly anchor: "data-anchor";
28
+ readonly anchorOcclude: "data-anchor-occlude";
29
+ readonly anchorOffset: "data-anchor-offset";
30
+ readonly set: "data-set";
31
+ readonly setValue: "data-set-value";
32
+ readonly click: "data-click";
33
+ readonly hoverIn: "data-hover-in";
34
+ readonly hoverOut: "data-hover-out";
35
+ };
36
+ /** Verbs per command kind. `toggle` for sequences and clips alternates forward / backward. */
37
+ export declare const UI_VERBS: Record<UiCommandKind, readonly string[]>;
38
+ export interface UiCommand {
39
+ kind: UiCommandKind;
40
+ verb: string;
41
+ ref: string;
42
+ }
43
+ /** A condition on scene state: `action:key`, `!sequence:key`, `ui:#panel`. */
44
+ export interface UiCondition {
45
+ negate: boolean;
46
+ kind: UiCommandKind;
47
+ ref: string;
48
+ }
49
+ export interface UiBind {
50
+ kind: 'sequence' | 'clip';
51
+ ref: string;
52
+ }
53
+ export interface UiAnchor {
54
+ /** Object ref (id or name). */
55
+ object: string;
56
+ /** Name of a node inside a model. */
57
+ node?: string;
58
+ }
59
+ /** What `data-set` writes to: an object (or a node of a model), or one of the environment elements. */
60
+ export declare const UI_SET_ENVIRONMENT: readonly ["camera", "sky", "clouds", "ocean", "terrain"];
61
+ export interface UiSetTarget {
62
+ /** `camera` / `sky` / `clouds` / `ocean` / `terrain`, or an object ref (key, id or name). */
63
+ target: string;
64
+ /** Name of a node inside a model. */
65
+ node?: string;
66
+ /** Settings path, the same ones clip tracks use: `materialSettings.opacity`, `meshSettings.position.y`, `config.intensity`. */
67
+ path: string;
68
+ }
69
+ /** `engine:materialSettings.opacity`, `engine/Door_L:meshSettings.rotation.y`, `sky:elevation`. */
70
+ export declare function parseSetTarget(value: string | null | undefined): {
71
+ set?: UiSetTarget;
72
+ error?: string;
73
+ };
74
+ /** The value of `data-set-value`: a number, true/false, a JSON array (`[1,0,0]`), or a string (`#ff0000`). */
75
+ export declare function parseSetValue(value: string | null | undefined): number | boolean | string | number[] | undefined;
76
+ /** `action.toggle:open-door sequence.play:intro` */
77
+ export declare function parseCommands(value: string | null | undefined): {
78
+ commands: UiCommand[];
79
+ errors: string[];
80
+ };
81
+ /** `action:open-door !sequence:intro ui:#menu` — all must hold. */
82
+ export declare function parseConditions(value: string | null | undefined): {
83
+ conditions: UiCondition[];
84
+ errors: string[];
85
+ };
86
+ /** `sequence:intro` or `clip:spin` (for <input type="range">). */
87
+ export declare function parseBind(value: string | null | undefined): {
88
+ bind?: UiBind;
89
+ error?: string;
90
+ };
91
+ /** `object:engine` or `object:engine/Door_L` (the node name is everything after the first slash). */
92
+ export declare function parseAnchor(value: string | null | undefined): {
93
+ anchor?: UiAnchor;
94
+ error?: string;
95
+ };
96
+ /** `12 -8` (px, from the projected point). */
97
+ export declare function parseOffset(value: string | null | undefined): [number, number] | null;
98
+ export declare const UI_CLASS_PREFIX = "rise-";
99
+ export interface UiClassInfo {
100
+ name: string;
101
+ /** What it is for — shown in the agent docs. */
102
+ use: string;
103
+ /** Elements the validator expects it on (warns when a control has none of the generic classes). */
104
+ for?: string;
105
+ }
106
+ export declare const UI_CLASSES: readonly UiClassInfo[];
107
+ export interface UiVariableInfo {
108
+ name: string;
109
+ default: string;
110
+ use: string;
111
+ }
112
+ /** Set on the page (or the host element) to restyle every layer at once. */
113
+ export declare const UI_VARIABLES: readonly UiVariableInfo[];
114
+ /** Class of the host element each layer is rendered in (target it from the page to override variables). */
115
+ export declare const UI_HOST_CLASS = "rise-ui-layer";
116
+ //# sourceMappingURL=uiSpec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uiSpec.d.ts","sourceRoot":"","sources":["../../src/ui/uiSpec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,173 @@
1
+ // =============================================================================
2
+ // UI layers — the spec (types, vocabulary, attribute grammar)
3
+ // =============================================================================
4
+ //
5
+ // A UI layer is HTML + CSS written by the AI agent (or by hand) that controls the
6
+ // scene through data attributes. The viewer's binder turns the attributes into calls
7
+ // on the project's SceneHandle (actions, clips, sequences). This file is the single
8
+ // source of the vocabulary: the binder, the validator, the agent docs and the base
9
+ // stylesheet all read it, so they cannot drift apart.
10
+ //
11
+ // <button class="rise-button" data-click="action.toggle:open-door">Open</button>
12
+ //
13
+ // Refs are an item's key, then its id, then its exact name (same as SceneHandle).
14
+ // Commands are separated by spaces, so a ref used in an attribute cannot contain one:
15
+ // address things by key.
16
+ //
17
+ /** Attributes that run commands. */
18
+ export const UI_TRIGGER_ATTRS = {
19
+ click: 'data-click',
20
+ hoverIn: 'data-hover-in',
21
+ hoverOut: 'data-hover-out',
22
+ };
23
+ export const UI_ATTR = {
24
+ ...UI_TRIGGER_ATTRS,
25
+ bind: 'data-bind',
26
+ state: 'data-state',
27
+ showWhen: 'data-show-when',
28
+ anchor: 'data-anchor',
29
+ anchorOcclude: 'data-anchor-occlude',
30
+ anchorOffset: 'data-anchor-offset',
31
+ set: 'data-set',
32
+ setValue: 'data-set-value',
33
+ };
34
+ /** Verbs per command kind. `toggle` for sequences and clips alternates forward / backward. */
35
+ export const UI_VERBS = {
36
+ action: ['apply', 'revert', 'toggle'],
37
+ sequence: ['play', 'reverse', 'pause', 'resume', 'stop', 'toggle'],
38
+ clip: ['play', 'reverse', 'pause', 'resume', 'stop', 'toggle'],
39
+ ui: ['show', 'hide', 'toggle'],
40
+ };
41
+ /** What `data-set` writes to: an object (or a node of a model), or one of the environment elements. */
42
+ export const UI_SET_ENVIRONMENT = ['camera', 'sky', 'clouds', 'ocean', 'terrain'];
43
+ /** `engine:materialSettings.opacity`, `engine/Door_L:meshSettings.rotation.y`, `sky:elevation`. */
44
+ export function parseSetTarget(value) {
45
+ // One target per attribute, so (unlike the command lists) names may contain spaces.
46
+ const m = /^([^:/]+?)(?:\/([^:]+?))?:([A-Za-z_][\w.]*)$/.exec((value ?? '').trim());
47
+ return m
48
+ ? { set: { target: m[1].trim(), node: m[2]?.trim(), path: m[3] } }
49
+ : { error: `"${value ?? ''}" is not target:path (e.g. engine:materialSettings.opacity or sky:elevation).` };
50
+ }
51
+ /** The value of `data-set-value`: a number, true/false, a JSON array (`[1,0,0]`), or a string (`#ff0000`). */
52
+ export function parseSetValue(value) {
53
+ if (value == null)
54
+ return undefined;
55
+ const t = value.trim();
56
+ if (t === 'true')
57
+ return true;
58
+ if (t === 'false')
59
+ return false;
60
+ if (/^-?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i.test(t))
61
+ return Number(t);
62
+ if (t.startsWith('[')) {
63
+ try {
64
+ const a = JSON.parse(t);
65
+ if (Array.isArray(a) && a.every(n => typeof n === 'number'))
66
+ return a;
67
+ }
68
+ catch { /* fall through */ }
69
+ }
70
+ return t;
71
+ }
72
+ const KINDS = ['action', 'sequence', 'clip', 'ui'];
73
+ const isKind = (k) => KINDS.includes(k);
74
+ const tokens = (value) => (value ?? '').split(/\s+/).filter(Boolean);
75
+ /** `action.toggle:open-door sequence.play:intro` */
76
+ export function parseCommands(value) {
77
+ const commands = [];
78
+ const errors = [];
79
+ for (const token of tokens(value)) {
80
+ const m = /^([a-z]+)\.([a-z]+):(.+)$/.exec(token);
81
+ if (!m) {
82
+ errors.push(`"${token}" is not kind.verb:ref (e.g. action.toggle:open-door).`);
83
+ continue;
84
+ }
85
+ const [, kind, verb, ref] = m;
86
+ if (!isKind(kind)) {
87
+ errors.push(`"${token}": unknown kind "${kind}". Use ${KINDS.join(', ')}.`);
88
+ continue;
89
+ }
90
+ if (!UI_VERBS[kind].includes(verb)) {
91
+ errors.push(`"${token}": ${kind} has no verb "${verb}". Use ${UI_VERBS[kind].join(', ')}.`);
92
+ continue;
93
+ }
94
+ if (kind === 'ui' && !/^#[A-Za-z][\w-]*$/.test(ref)) {
95
+ errors.push(`"${token}": ui commands address an element id in this layer, like ui.toggle:#menu.`);
96
+ continue;
97
+ }
98
+ commands.push({ kind, verb, ref });
99
+ }
100
+ return { commands, errors };
101
+ }
102
+ /** `action:open-door !sequence:intro ui:#menu` — all must hold. */
103
+ export function parseConditions(value) {
104
+ const conditions = [];
105
+ const errors = [];
106
+ for (const raw of tokens(value)) {
107
+ const negate = raw.startsWith('!');
108
+ const token = negate ? raw.slice(1) : raw;
109
+ const m = /^([a-z]+):(.+)$/.exec(token);
110
+ if (!m || !isKind(m[1])) {
111
+ errors.push(`"${raw}" is not [!]kind:ref (e.g. action:open-door).`);
112
+ continue;
113
+ }
114
+ if (m[1] === 'ui' && !/^#[A-Za-z][\w-]*$/.test(m[2])) {
115
+ errors.push(`"${raw}": ui conditions address an element id in this layer, like ui:#menu.`);
116
+ continue;
117
+ }
118
+ conditions.push({ negate, kind: m[1], ref: m[2] });
119
+ }
120
+ return { conditions, errors };
121
+ }
122
+ /** `sequence:intro` or `clip:spin` (for <input type="range">). */
123
+ export function parseBind(value) {
124
+ const m = /^(sequence|clip):(.+)$/.exec((value ?? '').trim());
125
+ return m ? { bind: { kind: m[1], ref: m[2] } } : { error: `"${value ?? ''}" is not sequence:ref or clip:ref.` };
126
+ }
127
+ /** `object:engine` or `object:engine/Door_L` (the node name is everything after the first slash). */
128
+ export function parseAnchor(value) {
129
+ const m = /^object:([^/]+)(?:\/(.+))?$/.exec((value ?? '').trim());
130
+ return m ? { anchor: { object: m[1], node: m[2] } } : { error: `"${value ?? ''}" is not object:ref or object:ref/NodeName.` };
131
+ }
132
+ /** `12 -8` (px, from the projected point). */
133
+ export function parseOffset(value) {
134
+ const p = tokens(value).map(Number);
135
+ return p.length === 2 && p.every(Number.isFinite) ? [p[0], p[1]] : null;
136
+ }
137
+ // ---------------------------------------------------------------------------
138
+ // Generic classes + variables (the base stylesheet in the viewer implements them)
139
+ // ---------------------------------------------------------------------------
140
+ //
141
+ // The prefix is `rise-`, not `3drise-`: a CSS class cannot start with a digit
142
+ // without escaping (`.3drise-button` is invalid; it has to be `.\33 drise-button`).
143
+ export const UI_CLASS_PREFIX = 'rise-';
144
+ export const UI_CLASSES = [
145
+ { name: 'rise-panel', use: 'A container grouping controls (a bar, a drawer, a side panel).' },
146
+ { name: 'rise-button', use: 'A button. Pressed / active state: [data-on="true"] when it has data-state.', for: 'button' },
147
+ { name: 'rise-toggle', use: 'A button showing an on/off state (use with data-state).', for: 'button' },
148
+ { name: 'rise-slider', use: 'A range input (<input type="range">), often with data-bind.', for: 'input[type=range]' },
149
+ { name: 'rise-input', use: 'A text or number input.', for: 'input' },
150
+ { name: 'rise-menu', use: 'A list of choices (a column of rise-menu-item).' },
151
+ { name: 'rise-menu-item', use: 'One choice in a rise-menu. Selected: [data-on="true"].' },
152
+ { name: 'rise-card', use: 'An info card.' },
153
+ { name: 'rise-card-title', use: 'The card heading.' },
154
+ { name: 'rise-card-body', use: 'The card text.' },
155
+ { name: 'rise-card-image', use: 'An <img> in a card.' },
156
+ { name: 'rise-hotspot', use: 'A marker anchored to an object or model part (data-anchor).' },
157
+ { name: 'rise-label', use: 'A small text label.' },
158
+ ];
159
+ /** Set on the page (or the host element) to restyle every layer at once. */
160
+ export const UI_VARIABLES = [
161
+ { name: '--rise-accent', default: '#6ea8ff', use: 'Accent colour: active states, slider thumb, hotspot.' },
162
+ { name: '--rise-accent-text', default: '#0b1220', use: 'Text on the accent colour.' },
163
+ { name: '--rise-bg', default: 'rgba(18, 22, 32, 0.72)', use: 'Control and card background.' },
164
+ { name: '--rise-text', default: '#f2f5fa', use: 'Text colour.' },
165
+ { name: '--rise-muted', default: 'rgba(242, 245, 250, 0.65)', use: 'Secondary text.' },
166
+ { name: '--rise-border', default: 'rgba(255, 255, 255, 0.16)', use: 'Border colour.' },
167
+ { name: '--rise-radius', default: '10px', use: 'Corner radius.' },
168
+ { name: '--rise-gap', default: '8px', use: 'Spacing between grouped controls.' },
169
+ { name: '--rise-font', default: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif', use: 'Font family (system fonts only).' },
170
+ { name: '--rise-blur', default: '12px', use: 'Backdrop blur of panels and cards.' },
171
+ ];
172
+ /** Class of the host element each layer is rendered in (target it from the page to override variables). */
173
+ export const UI_HOST_CLASS = 'rise-ui-layer';
@@ -0,0 +1,3 @@
1
+ import type { CreatedObjectSettings } from '../types/scene3d';
2
+ export declare function migrateEnvironmentObject<T extends CreatedObjectSettings>(obj: T): T;
3
+ //# sourceMappingURL=environmentMigration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environmentMigration.d.ts","sourceRoot":"","sources":["../../src/utils/environmentMigration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAgB9D,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,qBAAqB,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAkCnF"}
@@ -0,0 +1,50 @@
1
+ // =============================================================================
2
+ // Environment objects — load-time migration of the legacy rain
3
+ // =============================================================================
4
+ //
5
+ // Rain used to sit outside the environment family, with its own animation domain:
6
+ // its keyframes in `animations.rain`, its pointer bindings in `mouseMove.rain`.
7
+ // It is a family member now, driven through `animations.environment` /
8
+ // `mouseMove.environment` like every other environment object, so a saved rain's
9
+ // tracks move over (appended to anything already there). The never-rendered
10
+ // `splash` knob is dropped.
11
+ //
12
+ // Pure and idempotent: returns the same object when there is nothing to migrate.
13
+ //
14
+ export function migrateEnvironmentObject(obj) {
15
+ if (!obj || obj.type !== 'environment')
16
+ return obj;
17
+ const config = obj.config;
18
+ if (!config || config.type !== 'rain')
19
+ return obj;
20
+ const animations = obj.animations;
21
+ const mouseMove = obj.mouseMove;
22
+ const hasAnim = !!animations && 'rain' in animations;
23
+ const hasMouse = !!mouseMove && typeof mouseMove === 'object' && 'rain' in mouseMove;
24
+ if (!('splash' in config) && !hasAnim && !hasMouse)
25
+ return obj;
26
+ const next = { ...obj };
27
+ if ('splash' in config) {
28
+ const { splash: _splash, ...rest } = config;
29
+ next.config = rest;
30
+ }
31
+ if (hasAnim) {
32
+ const { rain, ...others } = animations;
33
+ const env = others.environment ?? {};
34
+ next.animations = {
35
+ ...others,
36
+ environment: { ...rain, ...env, animations: [...(env.animations ?? []), ...(rain?.animations ?? [])] },
37
+ };
38
+ }
39
+ if (hasMouse) {
40
+ const { rain, ...others } = mouseMove;
41
+ const env = others.environment;
42
+ next.mouseMove = {
43
+ ...others,
44
+ environment: env
45
+ ? { ...env, enabled: env.enabled || !!rain?.enabled, properties: [...(env.properties ?? []), ...(rain?.properties ?? [])] }
46
+ : rain,
47
+ };
48
+ }
49
+ return next;
50
+ }
@@ -2,7 +2,7 @@ export interface AnimatableConfigProperty {
2
2
  value: string;
3
3
  label: string;
4
4
  }
5
- export type GenerativeFamily = 'particles' | 'lights' | 'effects' | 'grids';
5
+ export type GenerativeFamily = 'particles' | 'lights' | 'effects' | 'grids' | 'environment' | 'space' | 'land' | 'text';
6
6
  /** The animatable knobs of one generative object, by family and config.type. */
7
7
  export declare function getGenerativeAnimatable(family: GenerativeFamily, config: {
8
8
  type?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"generativeAnimatable.d.ts","sourceRoot":"","sources":["../../src/utils/generativeAnimatable.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,wBAAwB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAY5E,gFAAgF;AAChF,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAAG,IAAI,GAC7C,wBAAwB,EAAE,CAM5B;AAED,sFAAsF;AACtF,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAAG,IAAI,GAC7C,OAAO,CAGT"}
1
+ {"version":3,"file":"generativeAnimatable.d.ts","sourceRoot":"","sources":["../../src/utils/generativeAnimatable.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,wBAAwB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAoBxH,gFAAgF;AAChF,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAAG,IAAI,GAC7C,wBAAwB,EAAE,CAM5B;AAED,sFAAsF;AACtF,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAAG,IAAI,GAC7C,OAAO,CAGT"}
@@ -2,6 +2,10 @@ import { GENERATIVE_PARTICLES_ANIMATABLE } from "../data/particlesDefaults.js";
2
2
  import { LIGHTS_ANIMATABLE } from "../data/lightsDefaults.js";
3
3
  import { EFFECTS_ANIMATABLE } from "../data/effectsDefaults.js";
4
4
  import { GRIDS_ANIMATABLE } from "../data/gridsDefaults.js";
5
+ import { ENVIRONMENT_OBJECTS_ANIMATABLE } from "../data/environmentDefaults.js";
6
+ import { SPACE_OBJECTS_ANIMATABLE } from "../data/spaceDefaults.js";
7
+ import { LAND_OBJECTS_ANIMATABLE } from "../data/landDefaults.js";
8
+ import { TEXT_OBJECTS_ANIMATABLE } from "../data/textDefaults.js";
5
9
  // Partial: grids list only the types converted to the config pattern so far (effects list all).
6
10
  const FAMILIES = {
7
11
  particles: GENERATIVE_PARTICLES_ANIMATABLE,
@@ -10,6 +14,14 @@ const FAMILIES = {
10
14
  lights: LIGHTS_ANIMATABLE,
11
15
  effects: EFFECTS_ANIMATABLE,
12
16
  grids: GRIDS_ANIMATABLE,
17
+ // Weather objects (type 'environment'): snow… The legacy rain is not in this table.
18
+ environment: ENVIRONMENT_OBJECTS_ANIMATABLE,
19
+ // Celestial bodies and backdrops (type 'space'): stars, earth, solarSystem, shootingStars.
20
+ space: SPACE_OBJECTS_ANIMATABLE,
21
+ // Placeable patches of terrain (type 'land'): mountains, hills, dunes, canyon.
22
+ land: LAND_OBJECTS_ANIMATABLE,
23
+ // Animated text looks (type 'text', config.type set): handwriting, neonTube, fourierSketch. The legacy bitmap / 3D text is not in this table.
24
+ text: TEXT_OBJECTS_ANIMATABLE,
13
25
  };
14
26
  /** The animatable knobs of one generative object, by family and config.type. */
15
27
  export function getGenerativeAnimatable(family, config) {
@@ -1,6 +1,6 @@
1
1
  import type { MouseMoveInteraction, MouseMoveInteractions, ObjectAnimations } from "../types/objectSettings";
2
2
  import type { PropertyKind } from "../properties/types";
3
- export type AnimatableDomain = 'transform' | 'material' | 'edges' | 'light' | 'clouds' | 'rain' | 'particles' | 'effect' | 'grid';
3
+ export type AnimatableDomain = 'transform' | 'material' | 'edges' | 'light' | 'clouds' | 'rain' | 'particles' | 'effect' | 'grid' | 'environment' | 'space' | 'land' | 'text' | 'sky' | 'ocean' | 'terrain';
4
4
  export interface AnimatableProperty {
5
5
  value: string;
6
6
  label: string;