nodality 1.3.7 → 1.3.9

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 (245) hide show
  1. package/bin/nodality.mjs +1 -1
  2. package/dist/animator.cjs.js +1 -1
  3. package/dist/animator.cjs.js.LICENSE.txt +1 -1
  4. package/dist/animator.esm.js.LICENSE.txt +1 -1
  5. package/dist/audionew.cjs.js +1 -1
  6. package/dist/audionew.cjs.js.LICENSE.txt +1 -1
  7. package/dist/audionew.esm.js.LICENSE.txt +1 -1
  8. package/dist/base.cjs.js +1 -1
  9. package/dist/base.cjs.js.LICENSE.txt +1 -1
  10. package/dist/base.esm.js.LICENSE.txt +1 -1
  11. package/dist/beta-desktop-bar.cjs.js +1 -1
  12. package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
  13. package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
  14. package/dist/beta-mobile-bar.cjs.js +1 -1
  15. package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
  16. package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
  17. package/dist/bundle.umd.js +1 -1
  18. package/dist/bundle.umd.js.LICENSE.txt +1 -1
  19. package/dist/button.cjs.js +1 -1
  20. package/dist/button.cjs.js.LICENSE.txt +1 -1
  21. package/dist/button.esm.js.LICENSE.txt +1 -1
  22. package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
  23. package/dist/card-getter.esm.js.LICENSE.txt +1 -1
  24. package/dist/center.cjs.js +1 -1
  25. package/dist/center.cjs.js.LICENSE.txt +1 -1
  26. package/dist/center.esm.js.LICENSE.txt +1 -1
  27. package/dist/checkbox.cjs.js +1 -1
  28. package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
  29. package/dist/checkbox.esm.js.LICENSE.txt +1 -1
  30. package/dist/code.cjs.js +1 -1
  31. package/dist/code.cjs.js.LICENSE.txt +1 -1
  32. package/dist/code.esm.js.LICENSE.txt +1 -1
  33. package/dist/container.cjs.js +1 -1
  34. package/dist/container.cjs.js.LICENSE.txt +1 -1
  35. package/dist/container.esm.js.LICENSE.txt +1 -1
  36. package/dist/data-list.cjs.js.LICENSE.txt +1 -1
  37. package/dist/data-list.esm.js.LICENSE.txt +1 -1
  38. package/dist/designer.cjs.js +1 -1
  39. package/dist/designer.cjs.js.LICENSE.txt +1 -1
  40. package/dist/designer.esm.js +1 -1
  41. package/dist/designer.esm.js.LICENSE.txt +1 -1
  42. package/dist/element-mapper.cjs.js +1 -1
  43. package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
  44. package/dist/element-mapper.esm.js +1 -1
  45. package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
  46. package/dist/finalresult.esm.js +2 -2
  47. package/dist/finalresult.esm.js.LICENSE.txt +1 -1
  48. package/dist/flex-card.cjs.js +1 -1
  49. package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
  50. package/dist/flex-card.esm.js.LICENSE.txt +1 -1
  51. package/dist/flex-grid.cjs.js +1 -1
  52. package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
  53. package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
  54. package/dist/flex-row.cjs.js +1 -1
  55. package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
  56. package/dist/flex-row.esm.js.LICENSE.txt +1 -1
  57. package/dist/floating-input.cjs.js +1 -1
  58. package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
  59. package/dist/floating-input.esm.js.LICENSE.txt +1 -1
  60. package/dist/free.cjs.js +1 -1
  61. package/dist/free.cjs.js.LICENSE.txt +1 -1
  62. package/dist/free.esm.js.LICENSE.txt +1 -1
  63. package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
  64. package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
  65. package/dist/image-picker.cjs.js +1 -1
  66. package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
  67. package/dist/image-picker.esm.js.LICENSE.txt +1 -1
  68. package/dist/image.cjs.js +1 -1
  69. package/dist/image.cjs.js.LICENSE.txt +1 -1
  70. package/dist/image.esm.js.LICENSE.txt +1 -1
  71. package/dist/index.cjs.js +1 -1
  72. package/dist/index.cjs.js.LICENSE.txt +1 -1
  73. package/dist/index.d.ts +17 -2
  74. package/dist/index.esm.js +2 -2
  75. package/dist/index.esm.js.LICENSE.txt +1 -1
  76. package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
  77. package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
  78. package/dist/link-getter.cjs.js +1 -1
  79. package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
  80. package/dist/link-getter.esm.js.LICENSE.txt +1 -1
  81. package/dist/link.cjs.js +1 -1
  82. package/dist/link.cjs.js.LICENSE.txt +1 -1
  83. package/dist/link.esm.js.LICENSE.txt +1 -1
  84. package/dist/meta-adder.cjs.js +1 -1
  85. package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
  86. package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
  87. package/dist/modal-2025.cjs.js +1 -1
  88. package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
  89. package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
  90. package/dist/multiswitcher.cjs.js +1 -1
  91. package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
  92. package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
  93. package/dist/new-nav-bar.cjs.js +1 -1
  94. package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
  95. package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
  96. package/dist/picker.cjs.js +1 -1
  97. package/dist/picker.cjs.js.LICENSE.txt +1 -1
  98. package/dist/picker.esm.js.LICENSE.txt +1 -1
  99. package/dist/progress.cjs.js +1 -1
  100. package/dist/progress.cjs.js.LICENSE.txt +1 -1
  101. package/dist/progress.esm.js.LICENSE.txt +1 -1
  102. package/dist/radio.cjs.js.LICENSE.txt +1 -1
  103. package/dist/radio.esm.js.LICENSE.txt +1 -1
  104. package/dist/range.cjs.js.LICENSE.txt +1 -1
  105. package/dist/range.esm.js.LICENSE.txt +1 -1
  106. package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
  107. package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
  108. package/dist/side-bar.cjs.js +1 -1
  109. package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
  110. package/dist/side-bar.esm.js.LICENSE.txt +1 -1
  111. package/dist/side-nav-bar.cjs.js +1 -1
  112. package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
  113. package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
  114. package/dist/simple-bar.cjs.js +1 -1
  115. package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
  116. package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
  117. package/dist/slider-2025.cjs.js +1 -1
  118. package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
  119. package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
  120. package/dist/spacer.cjs.js +1 -1
  121. package/dist/spacer.cjs.js.LICENSE.txt +1 -1
  122. package/dist/spacer.esm.js.LICENSE.txt +1 -1
  123. package/dist/stack.cjs.js +1 -1
  124. package/dist/stack.cjs.js.LICENSE.txt +1 -1
  125. package/dist/stack.esm.js.LICENSE.txt +1 -1
  126. package/dist/stacker.cjs.js.LICENSE.txt +1 -1
  127. package/dist/stacker.esm.js.LICENSE.txt +1 -1
  128. package/dist/table.cjs.js +1 -1
  129. package/dist/table.cjs.js.LICENSE.txt +1 -1
  130. package/dist/table.esm.js.LICENSE.txt +1 -1
  131. package/dist/text-field.cjs.js +1 -1
  132. package/dist/text-field.cjs.js.LICENSE.txt +1 -1
  133. package/dist/text-field.esm.js.LICENSE.txt +1 -1
  134. package/dist/text.cjs.js +1 -1
  135. package/dist/text.cjs.js.LICENSE.txt +1 -1
  136. package/dist/text.esm.js.LICENSE.txt +1 -1
  137. package/dist/theme.cjs.js.LICENSE.txt +1 -1
  138. package/dist/theme.esm.js.LICENSE.txt +1 -1
  139. package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
  140. package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
  141. package/dist/ulist.cjs.js +1 -1
  142. package/dist/ulist.cjs.js.LICENSE.txt +1 -1
  143. package/dist/ulist.esm.js.LICENSE.txt +1 -1
  144. package/dist/video.cjs.js +1 -1
  145. package/dist/video.cjs.js.LICENSE.txt +1 -1
  146. package/dist/video.esm.js +1 -1
  147. package/dist/video.esm.js.LICENSE.txt +1 -1
  148. package/dist/wrap.cjs.js +1 -1
  149. package/dist/wrap.cjs.js.LICENSE.txt +1 -1
  150. package/dist/wrap.esm.js.LICENSE.txt +1 -1
  151. package/dist/zoom-card.cjs.js +1 -1
  152. package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
  153. package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
  154. package/layout/animator.js +1 -1
  155. package/layout/audio.js +1 -1
  156. package/layout/audionew.js +1 -1
  157. package/layout/base.js +1 -1
  158. package/layout/beta-desktop-bar.js +1 -1
  159. package/layout/beta-mobile-bar.js +1 -1
  160. package/layout/button.js +1 -1
  161. package/layout/center.js +1 -1
  162. package/layout/checkbox.js +1 -1
  163. package/layout/circle.js +1 -1
  164. package/layout/code.js +1 -1
  165. package/layout/container.js +1 -1
  166. package/layout/dropdown-2025.js +1 -1
  167. package/layout/flex-card.js +1 -1
  168. package/layout/flex-grid.js +1 -1
  169. package/layout/flex-row.js +1 -1
  170. package/layout/form-components/custom.js +1 -1
  171. package/layout/form-components/data-list.js +1 -1
  172. package/layout/form-components/floating-input.js +1 -1
  173. package/layout/form-components/form-all.js +1 -1
  174. package/layout/form-components/form.js +1 -1
  175. package/layout/form-components/image-picker.js +1 -1
  176. package/layout/form-components/picker.js +1 -1
  177. package/layout/form-components/radio.js +1 -1
  178. package/layout/form-components/radiogroup.js +1 -1
  179. package/layout/form-components/range.js +1 -1
  180. package/layout/free.js +1 -1
  181. package/layout/grid-overlay.js +296 -0
  182. package/layout/grid-switcher.js +1 -1
  183. package/layout/grid.js +1 -1
  184. package/layout/horizontal-scroller.js +1 -1
  185. package/layout/image.js +1 -1
  186. package/layout/index.js +5 -1
  187. package/layout/link.js +1 -1
  188. package/layout/list.js +1 -1
  189. package/layout/meta-adder.js +1 -1
  190. package/layout/modal-2025.js +1 -1
  191. package/layout/morph.js +1 -1
  192. package/layout/multiswitcher.js +1 -1
  193. package/layout/nav-bar.js +1 -1
  194. package/layout/nav-factor/custom-div.js +1 -1
  195. package/layout/new-nav-bar.js +1 -1
  196. package/layout/polygon.js +1 -1
  197. package/layout/prerender-site.js +1 -1
  198. package/layout/prerender.js +1 -1
  199. package/layout/progress.js +1 -1
  200. package/layout/row.js +1 -1
  201. package/layout/scroll-video.js +1 -1
  202. package/layout/side-bar.js +1 -1
  203. package/layout/side-nav-bar.js +1 -1
  204. package/layout/simple-bar.js +1 -1
  205. package/layout/slider-2025.js +1 -1
  206. package/layout/spacer.js +1 -1
  207. package/layout/stack.js +1 -1
  208. package/layout/svg.js +1 -1
  209. package/layout/switcher.js +1 -1
  210. package/layout/table.js +1 -1
  211. package/layout/text-field.js +1 -1
  212. package/layout/text.js +1 -1
  213. package/layout/ulist.js +1 -1
  214. package/layout/video.js +203 -38
  215. package/layout/wrap.js +1 -1
  216. package/layout/zoom-card.js +1 -1
  217. package/lib/agent-surface.js +1 -1
  218. package/lib/card-getter.js +1 -1
  219. package/lib/check-page.js +1 -1
  220. package/lib/codegen.js +1 -1
  221. package/lib/data.js +1 -1
  222. package/lib/designer.js +4 -2
  223. package/lib/element-mapper.js +36 -3
  224. package/lib/element-params.generated.js +2 -2
  225. package/lib/grid-geometry.js +137 -0
  226. package/lib/keyframe-animation.js +1 -1
  227. package/lib/link-getter.js +1 -1
  228. package/lib/morph-node.js +1 -1
  229. package/lib/normalize-spec.js +1 -1
  230. package/lib/parse-html.js +1 -1
  231. package/lib/raster-inspect.js +1 -1
  232. package/lib/raster-ops.js +42 -3
  233. package/lib/raster-presets.js +1 -1
  234. package/lib/resolve-refs.js +1 -1
  235. package/lib/scroll-video.js +1 -1
  236. package/lib/seo.js +1 -1
  237. package/lib/stacker.js +1 -1
  238. package/lib/suggest.js +1 -1
  239. package/lib/theme.js +1 -1
  240. package/lib/transform-anim.js +1 -1
  241. package/lib/transition.js +1 -1
  242. package/lib/validate-nodes.js +1 -1
  243. package/lib/webmcp-adapter.js +1 -1
  244. package/package.json +1 -1
  245. package/schema.json +164 -5
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/polygon.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/row.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/spacer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/stack.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/svg.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/table.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/text.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/ulist.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/video.js CHANGED
@@ -1,65 +1,230 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
6
6
 
7
- import {Animator} from "./animator.js";
7
+ import { Animator } from "./animator.js";
8
+ import { toObjectSource } from "../lib/codegen.js";
8
9
 
10
+ // Video — a <video> element as data.
11
+ //
12
+ // Two shapes, because the web has two:
13
+ //
14
+ // a player { url, controls: true } — the default, unchanged
15
+ // an ambient loop
16
+ // { sources: [...], autoplay: true, loop: true,
17
+ // muted: true, playsinline: true, controls: false }
18
+ //
19
+ // The loop shape is the one this component grew for. It carries the things a
20
+ // muted background clip cannot work without and that a page should not have to
21
+ // reach into the DOM to set: the playback attributes, several encodings behind
22
+ // one element, a poster to hold the layout while bytes arrive, playback that
23
+ // only runs while the element is on screen, and a still image instead of
24
+ // motion for anyone who has asked for less of it.
25
+ //
26
+ // Three behaviours are automatic, because getting them wrong is the norm:
27
+ //
28
+ // • `autoplay` implies `muted`. Every browser blocks an unmuted autoplay,
29
+ // and a clip that silently refuses to start is the hardest kind of bug to
30
+ // see. Pass `muted: false` explicitly to override and accept the block.
31
+ // • An autoplaying clip is paused while off screen and while the tab is
32
+ // hidden, and resumes when it comes back. Decoding video nobody is looking
33
+ // at is the single most expensive thing an ambient loop can do.
34
+ // • `prefers-reduced-motion: reduce` cancels autoplay and leaves the poster
35
+ // showing. The clip is still playable if it has controls.
36
+
37
+ const isPlayable = (v) => v && typeof v.play === "function";
38
+
39
+ // Enough to save callers writing the MIME type for the formats a site ships.
40
+ const MIME = {
41
+ mp4: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
42
+ m4v: "video/mp4",
43
+ mov: "video/quicktime",
44
+ webm: "video/webm",
45
+ ogv: "video/ogg",
46
+ ogg: "video/ogg",
47
+ };
48
+
49
+ const typeFor = (url) => {
50
+ const ext = String(url || "").split("?")[0].split("#")[0].split(".").pop().toLowerCase();
51
+ return MIME[ext] || "";
52
+ };
9
53
 
10
-
11
-
12
54
  class Video extends Animator {
13
- constructor(url, /*obj*/) {
55
+ constructor(url) {
14
56
  super();
15
57
  this.url = url;
58
+ this.options = {};
16
59
  this.res = null;
17
-
18
60
  this.setup();
19
61
  }
20
-
21
-
22
-
23
- setup(){
24
- this.res = document.createElement("video");
25
- this.res.setAttribute("src", this.url);
26
- this.res.setAttribute("controls", "controls");
62
+
63
+ setup() {
64
+ const el = document.createElement("video");
65
+ if (this.url != null) el.setAttribute("src", this.url);
66
+ // Controls stay on by default: a bare `new Video(url)` has always been
67
+ // a player, and pages depend on that. `controls: false` opts out.
68
+ el.setAttribute("controls", "controls");
69
+ this.res = el;
70
+ return this;
71
+ }
72
+
73
+ set(obj = {}) {
74
+ this.options = obj;
75
+ const v = this.res;
76
+
77
+ //@ video.sources: Several encodings of the same clip: [{url, type}] or plain url strings. The browser picks the first it can play, so order best-first (AV1, then HEVC, then H.264). `type` is inferred from the extension when omitted.
78
+ if (Array.isArray(obj.sources) && obj.sources.length) {
79
+ // A `src` attribute wins over <source> children, so it has to go.
80
+ v.removeAttribute("src");
81
+ for (const child of [...v.querySelectorAll("source")]) child.remove();
82
+ const list = obj.sources.concat(this.url != null ? [this.url] : []);
83
+ const seen = new Set();
84
+ for (const entry of list) {
85
+ const url = typeof entry === "string" ? entry : entry && entry.url;
86
+ if (!url || seen.has(url)) continue;
87
+ seen.add(url);
88
+ const source = document.createElement("source");
89
+ source.setAttribute("src", url);
90
+ const type = (typeof entry === "object" && entry.type) || typeFor(url);
91
+ if (type) source.setAttribute("type", type);
92
+ v.appendChild(source);
93
+ }
94
+ }
95
+
96
+ //@ video.poster: Image shown before the first frame decodes. Give one on any autoplaying clip: it is what holds the layout still and what reduced-motion visitors see instead of the video.
97
+ obj.poster && v.setAttribute("poster", obj.poster);
98
+
99
+ //@ video.preload: "none", "metadata" (a good default for a loop) or "auto".
100
+ obj.preload && v.setAttribute("preload", obj.preload);
101
+
102
+ //@ video.controls: Show the browser's playback controls. Default true — pass false for an ambient loop.
103
+ if (obj.controls === false) v.removeAttribute("controls");
104
+ else if (obj.controls === true) v.setAttribute("controls", "controls");
105
+
106
+ //@ video.loop: Restart when the clip ends.
107
+ obj.loop && (v.loop = true, v.setAttribute("loop", "loop"));
108
+
109
+ //@ video.playsinline: Play in place on iPhone instead of taking over the screen. Implied by `autoplay`; there is no reason to refuse it on a background loop.
110
+ const playsinline = obj.playsinline ?? obj.autoplay;
111
+ if (playsinline) {
112
+ v.setAttribute("playsinline", "");
113
+ v.setAttribute("webkit-playsinline", "");
114
+ }
115
+
116
+ //@ video.muted: Mute the audio track. Forced on by `autoplay`, because browsers block an unmuted autoplay; pass false only if you mean to accept that.
117
+ const muted = obj.muted ?? (obj.autoplay ? true : undefined);
118
+ if (muted === true) {
119
+ v.muted = true;
120
+ v.setAttribute("muted", "");
121
+ } else if (muted === false) {
122
+ v.muted = false;
123
+ v.removeAttribute("muted");
124
+ }
125
+
126
+ //@ video.autoplay: Start on its own. Implies muted and playsinline, pauses off screen and in a hidden tab, and is cancelled by prefers-reduced-motion (the poster stays).
127
+ //@ video.lazy: Only play while on screen. On by default whenever `autoplay` is set; pass false to keep an autoplaying clip running off screen.
128
+ if (obj.autoplay) this._autoplay(obj.lazy !== false);
129
+
130
+ //@ video.objectFit: How the frame fills the element box: "cover", "contain", "fill", "none".
131
+ obj.objectFit && (v.style.objectFit = obj.objectFit);
132
+
133
+ //@ video.aspectRatio: Box proportion held before the video loads, e.g. "16 / 9". Prevents the page shifting when the first frame arrives.
134
+ obj.aspectRatio && (v.style.aspectRatio = obj.aspectRatio);
135
+
136
+ //@ video.label: Accessible name, for a clip that carries meaning.
137
+ obj.label && v.setAttribute("aria-label", obj.label);
138
+
139
+ //@ video.decorative: Hide from assistive technology. For an ambient loop that repeats what the page already says in text.
140
+ if (obj.decorative) v.setAttribute("aria-hidden", "true");
141
+
142
+ //@ video.background: CSS background behind the frame — visible while the poster loads.
143
+ obj.background && (v.style.background = obj.background);
144
+ //@ video.height: CSS height of the element.
145
+ obj.height && (v.style.height = obj.height);
146
+ //@ video.maxWidth: CSS max-width of the element.
147
+ obj.maxWidth && (v.style.maxWidth = obj.maxWidth);
148
+ //@ video.radius: Corner radius. A CSS length; a bare number is read as pixels.
149
+ obj.radius && (v.style.borderRadius = typeof obj.radius === "number" ? `${obj.radius}px` : obj.radius);
150
+ //@ video.width: CSS width of the element.
151
+ obj.width && (v.style.width = `${obj.width}`);
152
+ //@ video.opacity: 0–1.
153
+ obj.opacity && (v.style.opacity = obj.opacity);
154
+
155
+ //@ video.raster: Raster op nodes to run over this element.
156
+ obj.raster && this.rasterize(obj.raster);
157
+
158
+ return this;
159
+ }
160
+
161
+ // Start playing, but only when it is reasonable to: motion is wanted, the
162
+ // element is on screen, and the tab is in front.
163
+ _autoplay(lazy) {
164
+ const v = this.res;
165
+ if (typeof window === "undefined" || !isPlayable(v)) return this;
166
+
167
+ const reduced = typeof window.matchMedia === "function" &&
168
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches;
169
+ if (reduced) {
170
+ // Deliberately NOT an autoplay attribute: the poster stays, and the
171
+ // clip remains playable by anyone who wants it.
172
+ v.removeAttribute("autoplay");
173
+ return this;
27
174
  }
28
175
 
176
+ v.setAttribute("autoplay", "");
177
+ // play() rejects when a policy blocks it. That is not an error worth
178
+ // throwing into a page — the poster is still there.
179
+ const play = () => { const p = v.play(); p && p.catch && p.catch(() => {}); };
29
180
 
30
- set(obj){
31
- this.options = obj;
32
- obj.radius && (this.res.style.borderRadius = obj.radius);
33
- obj.width && (this.res.style.width = `${obj.width}`);
34
- obj.opacity && (this.res.style.opacity = obj.opacity);
181
+ if (!lazy || typeof window.IntersectionObserver !== "function") {
182
+ play();
35
183
  return this;
36
184
  }
37
-
38
-
39
-
40
- size(w/*, h*/) {
41
-
42
- this.res.style.width = w;
43
-
44
-
45
185
 
186
+ this._visible = false;
187
+ const io = new window.IntersectionObserver((entries) => {
188
+ for (const entry of entries) {
189
+ this._visible = entry.isIntersecting;
190
+ if (entry.isIntersecting && !this._hidden) play();
191
+ else v.pause();
192
+ }
193
+ }, { threshold: 0.2 });
194
+ io.observe(v);
195
+ this._track(() => io.disconnect());
196
+
197
+ this._hidden = false;
198
+ this._on(document, "visibilitychange", () => {
199
+ this._hidden = document.hidden;
200
+ if (document.hidden) v.pause();
201
+ else if (this._visible) play();
202
+ });
203
+ return this;
204
+ }
205
+
206
+ play() { isPlayable(this.res) && this.res.play()?.catch?.(() => {}); return this; }
207
+ pause() { isPlayable(this.res) && this.res.pause(); return this; }
208
+
209
+ size(w) {
210
+ this.res.style.width = w;
46
211
  return this;
47
212
  }
48
-
213
+
49
214
  toCode() {
50
- const objString = JSON.stringify(this.options, null, 4);
51
- return [`new Video("${this.url}").set(${objString})`];
52
- }
53
-
54
- render(el) {
215
+ const cleaned = Object.fromEntries(
216
+ Object.entries(this.options || {}).filter(([, v]) => v != null)
217
+ );
218
+ return [`new Video("${this.url}").set(${toObjectSource(cleaned, 4)})`];
219
+ }
220
+
221
+ render(el) {
55
222
  if (el) {
56
- document.querySelector(el).appendChild(this.res);
57
- } else {
223
+ document.querySelector(el)?.appendChild(this.res);
58
224
  return this.res;
59
225
  }
226
+ return this.res;
60
227
  }
61
- }
62
-
63
-
64
-
228
+ }
229
+
65
230
  export { Video };
package/layout/wrap.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/check-page.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/codegen.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/data.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/designer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -75,6 +75,7 @@ import { ScrollVideo } from "../lib/scroll-video.js";
75
75
  import { AreaSwitcher } from "../layout/grid-switcher.js";
76
76
  import { Polygon } from "../layout/polygon.js";
77
77
  import { Circle } from "../layout/circle.js";
78
+ import { GridOverlay } from "../layout/grid-overlay.js";
78
79
 
79
80
 
80
81
 
@@ -1444,7 +1445,8 @@ new Slider(texts, null, { tintColor: "#e74c3c", inactiveColor: "#ccc" })
1444
1445
  const layout = {
1445
1446
  Text, Image, Link, FlexRow, UINavBar, Free, Audio, Progress, Center, Code,
1446
1447
  Stack, Wrapper, MetaAdder, Table, Dropdown, Modal, TextField, Card,
1447
- Wrap, FlexGrid, ZoomCard, Switcher, MobileBar, DesktopBar, SideNav, Spacer, HScroller, Polygon, Circle, UList
1448
+ Wrap, FlexGrid, ZoomCard, Switcher, MobileBar, DesktopBar, SideNav, Spacer, HScroller, Polygon, Circle, UList,
1449
+ GridOverlay
1448
1450
  };
1449
1451
 
1450
1452
  const formComponents = {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -70,6 +70,7 @@ import { ScrollVideo } from "../lib/scroll-video.js";
70
70
  import { AreaSwitcher } from "../layout/grid-switcher.js";
71
71
  import { Polygon } from "../layout/polygon.js";
72
72
  import { Circle } from "../layout/circle.js";
73
+ import { GridOverlay } from "../layout/grid-overlay.js";
73
74
 
74
75
 
75
76
  // The closed set of `type:` values mapType understands, in the order it
@@ -81,6 +82,7 @@ const ELEMENT_TYPES = [
81
82
  "radio", "input", "labelInput", "filePicker", "picker", "video",
82
83
  "audio", "multiswitcher", "button", "form", "checkbox", "stack",
83
84
  "simple", "copy", "wrap", "circle", "polygon", "code", "table", "ulist",
85
+ "gridOverlay",
84
86
  ];
85
87
 
86
88
  // A nav entry is {title, link}. A bare string is accepted as the title: that
@@ -248,6 +250,8 @@ class ElementMapper { // 22:09:58 04/11/2024
248
250
  return this.mapTable(obj);
249
251
  } else if (obj.el.type === "ulist"){
250
252
  return this.mapUList(obj);
253
+ } else if (obj.el.type === "gridOverlay"){
254
+ return this.mapGridOverlay(obj);
251
255
  }
252
256
 
253
257
  // Everything above returned. Falling through used to return
@@ -692,6 +696,37 @@ animation: {
692
696
 
693
697
 
694
698
 
699
+ // A blueprint grid over the page. Only the keys a spec actually set are
700
+ // passed on: GridOverlay supplies its own defaults for the rest, and an
701
+ // explicit `undefined` would also surface in toCode().
702
+ //
703
+ // Kept ABOVE `dropdown` on purpose. scripts/generate-schema.mjs finds a
704
+ // method's body by counting braces after stripping `//` comments, and the
705
+ // strip also cuts `//` inside URL strings, dropping their closing braces.
706
+ // The bodies of dropdown, protoNav and sideNav therefore run to the end of
707
+ // the class, so a mapper placed below them has its parameters credited to
708
+ // those three types as well.
709
+ static mapGridOverlay(obj){
710
+ const el = obj.el;
711
+ const options = {
712
+ id: el.id,
713
+ columns: el.columns, rows: el.rows, inset: el.inset, color: el.color,
714
+ lineWidth: el.lineWidth, lineOpacity: el.lineOpacity,
715
+ marks: el.marks, markSize: el.markSize,
716
+ position: el.position, zIndex: el.zIndex,
717
+ font: el.font, fontSize: el.fontSize, letterSpacing: el.letterSpacing,
718
+ uppercase: el.uppercase, breakpoint: el.breakpoint,
719
+ mobile: el.mobile, readouts: el.readouts,
720
+ raster: this.filteroRaster(el.id, obj.customOptions),
721
+ };
722
+ for (const key of Object.keys(options)) {
723
+ if (options[key] == null) delete options[key];
724
+ }
725
+ return new GridOverlay().set(options);
726
+ }
727
+
728
+
729
+
695
730
  static dropdown(obj){
696
731
  // A label for the trigger, which is what makes every declared option
697
732
  // selectable. Without one the first item becomes the label and is
@@ -1648,8 +1683,6 @@ let elo = new Polygon({ id: "hex" })
1648
1683
 
1649
1684
  return elo;
1650
1685
  }
1651
-
1652
-
1653
1686
  static mapText(obj) {
1654
1687
  // console.log(obj.el.type);
1655
1688
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.7
2
+ * nodality v1.3.9
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -8,4 +8,4 @@
8
8
  // Every parameter name any element type reads, recovered from source.
9
9
  // Used for near-miss typo detection in validate-nodes.js. A drift test
10
10
  // regenerates this and fails if it differs, so it cannot rot silently.
11
- export const ELEMENT_PARAM_NAMES = ["absolute","accept","action","align","alignIts","alignTo","aligns","alt","animate","animation","area","arrayMargin","arrayPadding","as","at","auto","backTo","background","backgroundCard","backgroundColor","backgroundOp","behaviour","blast","blastData","block","bold","border","borderObj","borderObja","borderRadius","brand","breakWord","breakpoints","cellAlign","cellPadding","center","centerColumn","centerSelf","centered","checkedBackgroundColor","child","children","clampc","class","clipPath","code","colat","color","column","contentWidth","cursor","customAlign","customJustify","customStyle","data","decoding","diameter","disp","dropdown","em","exact","fetchPriority","filter","filtera","fitContent","fixMobileTap","fixed","flex","flexCenter","flexDir","fluid","fluidc","font","frame","ga","gap","gpos","gradient","hamColour","hamburgerColour","headStyle","height","hideField","hideOverflow","hover","icon","id","img","index","initLetter","inline","inpad","inputType","isBackground","isFull","isHidden","isLink","isNavA","italic","items","justifo","justify","keySet","kind","label","layout","left","length","link","loading","map","mar","margin","marginOp","maxHeight","maxWidth","mboth","method","minHeight","mobile","multiple","name","new","nowrap","objectFit","objectPosition","offcanvas","okayColor","onScroll","onTap","opacity","overflow","owrap","pad","padding","placeholder","position","positions","preffersId","radius","ranges","raster","react","removeDecoration","required","resmar","respad","responsive","resprop","rounded","scale","sequence","shadow","sides","simpleBorder","simpleCenter","size","sizes","span","srcset","sticky","stretch","stroke","style","stype","svg","tag","tags","templateCols","text","theme","tint","title","toColumn","top","transform","transition","type","url","useBrightness","value","vtn","weight","width","wrap","x","y","zIndex"];
11
+ export const ELEMENT_PARAM_NAMES = ["absolute","accept","action","align","alignIts","alignTo","aligns","alt","animate","animation","area","arrayMargin","arrayPadding","as","aspectRatio","at","auto","autoplay","backTo","background","backgroundCard","backgroundColor","backgroundOp","behaviour","blast","blastData","block","bold","border","borderObj","borderObja","borderRadius","brand","breakWord","breakpoint","breakpoints","cellAlign","cellPadding","center","centerColumn","centerSelf","centered","checkedBackgroundColor","child","children","clampc","class","clipPath","code","colat","color","column","columns","contentWidth","controls","cursor","customAlign","customJustify","customStyle","data","decoding","decorative","diameter","disp","dropdown","em","exact","fetchPriority","filter","filtera","fitContent","fixMobileTap","fixed","flex","flexCenter","flexDir","fluid","fluidc","font","fontSize","frame","ga","gap","gpos","gradient","hamColour","hamburgerColour","headStyle","height","hideField","hideOverflow","hover","icon","id","img","index","initLetter","inline","inpad","inputType","inset","isBackground","isFull","isHidden","isLink","isNavA","italic","items","justifo","justify","keySet","kind","label","layout","lazy","left","length","letterSpacing","lineOpacity","lineWidth","link","loading","loop","map","mar","margin","marginOp","markSize","marks","maxHeight","maxWidth","mboth","method","minHeight","mobile","multiple","muted","name","new","nowrap","objectFit","objectPosition","offcanvas","okayColor","onScroll","onTap","opacity","overflow","owrap","pad","padding","placeholder","playsinline","position","positions","poster","preffersId","preload","radius","ranges","raster","react","readouts","removeDecoration","required","resmar","respad","responsive","resprop","rounded","rows","scale","sequence","shadow","sides","simpleBorder","simpleCenter","size","sizes","sources","span","srcset","sticky","stretch","stroke","style","stype","svg","tag","tags","templateCols","text","theme","tint","title","toColumn","top","transform","transition","type","uppercase","url","useBrightness","value","vtn","weight","width","wrap","x","y","zIndex"];