nodality 1.0.166 → 1.0.168

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 (98) hide show
  1. package/bin/nodality.js +312 -0
  2. package/layout/animator.js +1 -1
  3. package/layout/audio.js +1 -1
  4. package/layout/audionew.js +1 -1
  5. package/layout/base-2.js +1 -1
  6. package/layout/base.js +1 -1
  7. package/layout/beta-desktop-bar.js +1 -1
  8. package/layout/beta-mobile-bar.js +1 -1
  9. package/layout/box.js +1 -1
  10. package/layout/button.js +1 -1
  11. package/layout/cards.js +1 -1
  12. package/layout/center.js +1 -1
  13. package/layout/checkbox.js +1 -1
  14. package/layout/circle.js +1 -1
  15. package/layout/clean-row.js +1 -1
  16. package/layout/code.js +1 -1
  17. package/layout/container.js +1 -1
  18. package/layout/custom.js +1 -1
  19. package/layout/div-image.js +1 -1
  20. package/layout/dropdown-2025.js +1 -1
  21. package/layout/dropdown.js +1 -1
  22. package/layout/empty-element.js +1 -1
  23. package/layout/external-stylesheet.js +1 -1
  24. package/layout/flex-card.js +1 -1
  25. package/layout/flex-grid.js +1 -1
  26. package/layout/flex-row.js +1 -1
  27. package/layout/footer.js +1 -1
  28. package/layout/form-components/custom.js +1 -1
  29. package/layout/form-components/data-list.js +1 -1
  30. package/layout/form-components/floating-input.js +1 -1
  31. package/layout/form-components/form-all.js +1 -1
  32. package/layout/form-components/form.js +1 -1
  33. package/layout/form-components/image-picker.js +1 -1
  34. package/layout/form-components/picker.js +1 -1
  35. package/layout/form-components/radio.js +1 -1
  36. package/layout/form-components/radiogroup.js +1 -1
  37. package/layout/form-components/range.js +1 -1
  38. package/layout/free.js +1 -1
  39. package/layout/grid-new.js +1 -1
  40. package/layout/grid-switcher.js +1 -1
  41. package/layout/grid.js +1 -1
  42. package/layout/group.js +1 -1
  43. package/layout/header.js +1 -1
  44. package/layout/horizontal-scroller.js +1 -1
  45. package/layout/image-old.js +1 -1
  46. package/layout/image.js +1 -1
  47. package/layout/index.js +1 -1
  48. package/layout/label.js +1 -1
  49. package/layout/link.js +1 -1
  50. package/layout/list-OLD.js +1 -1
  51. package/layout/list.js +1 -1
  52. package/layout/meta-adder.js +1 -1
  53. package/layout/modal-2025.js +1 -1
  54. package/layout/modernwrap.js +1 -1
  55. package/layout/multiswitcher.js +1 -1
  56. package/layout/multiswitcherBeta.js +1 -1
  57. package/layout/nav-bar.js +1 -1
  58. package/layout/nav-factor/custom-div.js +1 -1
  59. package/layout/navBar-OLD.js +1 -1
  60. package/layout/new-flat-adder.js +1 -1
  61. package/layout/new-nav-bar.js +1 -1
  62. package/layout/offset-container.js +1 -1
  63. package/layout/polygon.js +1 -1
  64. package/layout/prerender-site.js +16 -2
  65. package/layout/prerender.js +15 -5
  66. package/layout/progress.js +1 -1
  67. package/layout/row.js +1 -1
  68. package/layout/saved-new-nav-bar.js +1 -1
  69. package/layout/scroll-video.js +1 -1
  70. package/layout/side-bar.js +1 -1
  71. package/layout/side-nav-bar.js +1 -1
  72. package/layout/simple-bar.js +1 -1
  73. package/layout/slider-2025.js +1 -1
  74. package/layout/spacer.js +1 -1
  75. package/layout/stack.js +1 -1
  76. package/layout/styler.js +1 -1
  77. package/layout/svg.js +1 -1
  78. package/layout/switcher.js +1 -1
  79. package/layout/table.js +1 -1
  80. package/layout/text-field.js +1 -1
  81. package/layout/text.js +1 -1
  82. package/layout/ulist.js +1 -1
  83. package/layout/video.js +1 -1
  84. package/layout/without-new.js +1 -1
  85. package/layout/wrap.js +1 -1
  86. package/layout/zoom-card.js +1 -1
  87. package/lib/card-getter.js +1 -1
  88. package/lib/data.js +48 -0
  89. package/lib/designer.js +1 -1
  90. package/lib/element-mapper.js +1 -1
  91. package/lib/keyframe-animation.js +1 -1
  92. package/lib/link-getter.js +1 -1
  93. package/lib/scroll-video.js +1 -1
  94. package/lib/seo.js +198 -0
  95. package/lib/stacker.js +1 -1
  96. package/lib/theme.js +1 -1
  97. package/lib/transform-anim.js +1 -1
  98. package/package.json +4 -2
package/bin/nodality.js CHANGED
@@ -40,6 +40,9 @@ function showUsage() {
40
40
  console.log(`Usage:
41
41
  nodality prerender [flags] # SSG: render upload/*.html in place
42
42
  nodality compile [src/<file>.js] [flags] # Emit Designer output as a companion file
43
+ nodality fanout # Generate per-item pages from JSON (reads
44
+ # the \`fanout\` block in nodality.config.json;
45
+ # runs automatically before \`prerender\`)
43
46
  nodality help
44
47
 
45
48
  Compile flags:
@@ -162,6 +165,280 @@ function loadConfigFile(cwd) {
162
165
  }
163
166
  }
164
167
 
168
+ // ─── Fanout (data-driven page expansion) ────────────────────────
169
+
170
+ /**
171
+ * Generate one HTML + entry-script pair per item in a JSON dataset.
172
+ * The classic use case is a "detail" template that takes a query
173
+ * parameter at runtime (`/detail.html?id=helix`) and the SEO problem
174
+ * that crawlers see an empty mount for every product. Fanout reads
175
+ * the data once at build time, walks an array within it, and writes
176
+ * a per-item static page that the prerender step then turns into
177
+ * fully populated HTML.
178
+ *
179
+ * Configured in `nodality.config.json`:
180
+ *
181
+ * "fanout": [
182
+ * {
183
+ * "template": "detail.html",
184
+ * "data": "products.json",
185
+ * "items": "categories[].products", // dot path with [] for flatMap
186
+ * "id": "id", // field on each item
187
+ * "title": "SLS3 — {name}", // {name} = item.name, {id} etc.
188
+ * "entry": "pages/detail.js", // exported renderDetailPage(id)
189
+ * "bodyAttr": "data-product-id" // attr on <body> for the id
190
+ * }
191
+ * ]
192
+ *
193
+ * Output:
194
+ * • `upload/<basename>-<id>.html` — clone of the template, with
195
+ * `<title>` substituted and `<body data-...="<id>">`.
196
+ * • `upload/pages/<basename>-<id>.js` — thin wrapper that imports
197
+ * the entry's exported renderer and invokes it with this id.
198
+ *
199
+ * Stale outputs from a previous run (orphaned products etc.) are
200
+ * cleaned up before regenerating. The expansion runs once at the
201
+ * start of `nodality prerender` whenever the config has a `fanout`
202
+ * block; you can also invoke it standalone via `nodality fanout`.
203
+ */
204
+ function runFanout(cwd, uploadDir, fanoutConfig) {
205
+ if (!Array.isArray(fanoutConfig) || fanoutConfig.length === 0) return 0;
206
+
207
+ let totalWrote = 0;
208
+ for (const spec of fanoutConfig) {
209
+ const {
210
+ template, data, items, id = "id", title, entry,
211
+ bodyAttr = "data-product-id",
212
+ // ─── Auto-injected JSON-LD per item (1.0.168+) ─────────
213
+ // When `jsonLdType` is set, fanout emits a
214
+ // `<script type="application/ld+json" data-seo="1">` block per
215
+ // generated HTML with the right schema.org type and fields
216
+ // mapped from each item. Saves projects from importing
217
+ // nodality/seo and calling productJsonLd() by hand inside the
218
+ // page entry — useful because the page entry runs LATE (after
219
+ // the static HTML is already served) so crawlers that don't
220
+ // execute JS miss any client-injected structured data.
221
+ jsonLdType, // e.g. "Product" | "Article"
222
+ jsonLdFields, // map of schema-key → dot-path on item (e.g. { name: "name", image: "images.0", sku: "id" })
223
+ jsonLdExtra, // static fields to add verbatim (e.g. { brand: { "@type": "Brand", name: "SLS3" }, priceCurrency: "CZK" })
224
+ } = spec;
225
+ if (!template || !data) {
226
+ console.warn(`[nodality] fanout: skipping spec without template/data`);
227
+ continue;
228
+ }
229
+
230
+ const tplPath = path.join(uploadDir, template);
231
+ const dataPath = path.join(uploadDir, data);
232
+ if (!fs.existsSync(tplPath)) {
233
+ console.error(`[nodality] fanout: template missing: ${template}`);
234
+ process.exit(1);
235
+ }
236
+ if (!fs.existsSync(dataPath)) {
237
+ console.error(`[nodality] fanout: data missing: ${data}`);
238
+ process.exit(1);
239
+ }
240
+
241
+ const json = JSON.parse(fs.readFileSync(dataPath, "utf8"));
242
+ const list = resolveItemsPath(json, items ?? "");
243
+ if (!Array.isArray(list) || list.length === 0) {
244
+ console.warn(`[nodality] fanout: ${data} produced no items at path "${items}"`);
245
+ continue;
246
+ }
247
+
248
+ const templateHtml = fs.readFileSync(tplPath, "utf8");
249
+ const base = path.basename(template, ".html");
250
+ const pagesDir = path.join(uploadDir, "pages");
251
+ fs.mkdirSync(pagesDir, { recursive: true });
252
+
253
+ // Clean stale outputs from a previous run so removing an item
254
+ // also removes its page. Match by prefix; original template
255
+ // (e.g. `detail.html` itself) is never touched.
256
+ for (const f of fs.readdirSync(uploadDir)) {
257
+ if (new RegExp(`^${escapeRegex(base)}-.+\\.html$`).test(f)) {
258
+ fs.unlinkSync(path.join(uploadDir, f));
259
+ }
260
+ }
261
+ for (const f of fs.readdirSync(pagesDir)) {
262
+ if (new RegExp(`^${escapeRegex(base)}-.+\\.js$`).test(f)) {
263
+ fs.unlinkSync(path.join(pagesDir, f));
264
+ }
265
+ }
266
+
267
+ // Discover the entry file to mirror per-item if not given.
268
+ const entryRel = entry ?? `pages/${base}.js`;
269
+ const entryFull = path.join(uploadDir, entryRel);
270
+ if (!fs.existsSync(entryFull)) {
271
+ console.error(`[nodality] fanout: entry script missing: ${entryRel}`);
272
+ process.exit(1);
273
+ }
274
+ const entryBase = path.basename(entryRel, ".js");
275
+ const entryDir = path.dirname(entryRel);
276
+
277
+ let wrote = 0;
278
+ for (const item of list) {
279
+ const itemId = item?.[id];
280
+ if (!itemId || !/^[A-Za-z0-9][A-Za-z0-9-_]*$/.test(String(itemId))) {
281
+ console.warn(`[nodality] fanout: skipping item with bad ${id}: ${JSON.stringify(itemId)}`);
282
+ continue;
283
+ }
284
+
285
+ // HTML: rewrite <title>, inject body data-attr, point script src
286
+ // at the per-item entry.
287
+ const resolvedTitle = title
288
+ ? interpolate(title, item)
289
+ : String(item?.name ?? itemId);
290
+ const escTitle = escapeHtml(resolvedTitle);
291
+
292
+ let html = templateHtml
293
+ .replace(/<title>[^<]*<\/title>/i, `<title>${escTitle}</title>`)
294
+ .replace(/<body(\s[^>]*)?>/i, (_, attrs = "") => {
295
+ const cleaned = (attrs ?? "").replace(
296
+ new RegExp(`\\s+${escapeRegex(bodyAttr)}="[^"]*"`),
297
+ "",
298
+ );
299
+ return `<body${cleaned} ${bodyAttr}="${itemId}">`;
300
+ });
301
+
302
+ // Rewrite the original `<script src=".../pages/<entryBase>.js">`
303
+ // to point at the per-item wrapper.
304
+ const srcRx = new RegExp(
305
+ `(<script[^>]*src=")([^"]*\\/?)${escapeRegex(entryBase)}\\.js("[^>]*>)`,
306
+ "i",
307
+ );
308
+ html = html.replace(srcRx, `$1$2${entryBase}-${itemId}.js$3`);
309
+
310
+ // Auto-injected JSON-LD per item, if the spec asked for it.
311
+ // Inserted inside <head> (before </head>) so crawlers see it
312
+ // without executing the page entry — critical for Bing /
313
+ // social-card scrapers / non-Google crawlers that don't run JS.
314
+ if (jsonLdType && jsonLdFields) {
315
+ const ld = buildItemJsonLd(jsonLdType, jsonLdFields, jsonLdExtra, item);
316
+ const tag = `<script type="application/ld+json" data-seo="1">${
317
+ JSON.stringify(ld)
318
+ }</script>`;
319
+ html = html.replace(/<\/head>/i, `${tag}\n</head>`);
320
+ }
321
+
322
+ fs.writeFileSync(path.join(uploadDir, `${base}-${itemId}.html`), html);
323
+
324
+ // JS wrapper. The user's entry must export an async function
325
+ // named `renderDetailPage` (or, generically, the camelCase
326
+ // form of the basename + "Page"). We call it with the id.
327
+ const fnName = entryBase
328
+ .split(/[-_]/)
329
+ .map((p, i) => (i === 0 ? p : p[0].toUpperCase() + p.slice(1)))
330
+ .join("") + "Page"; // detail → renderDetailPage? No — detailPage
331
+ // To minimise convention surprises, default to the underscored
332
+ // `render<Pascal>Page` form too. Try both: the wrapper imports
333
+ // whichever the entry exports.
334
+ const pascal = entryBase
335
+ .split(/[-_]/)
336
+ .map((p) => p[0].toUpperCase() + p.slice(1))
337
+ .join("");
338
+ const wrapperBody = `// Auto-generated by \`nodality fanout\`.
339
+ // Per-item entry — invokes the renderer in ${entryRel}
340
+ // with this item's id baked in.
341
+ import * as mod from "./${path.basename(entryRel)}";
342
+
343
+ const fn = mod.render${pascal}Page ?? mod.${fnName} ?? mod.default;
344
+ if (typeof fn !== "function") {
345
+ throw new Error(
346
+ "[fanout] ${entryRel} must export render${pascal}Page(id) " +
347
+ "(or a default async function). nodality fanout could not find one.",
348
+ );
349
+ }
350
+ await fn(${JSON.stringify(String(itemId))});
351
+ `;
352
+ fs.writeFileSync(
353
+ path.join(pagesDir, `${entryBase}-${itemId}.js`),
354
+ wrapperBody,
355
+ );
356
+ wrote++;
357
+ }
358
+
359
+ console.log(`[nodality] fanout: ${template} × ${data} → ${wrote} page(s)`);
360
+ totalWrote += wrote;
361
+ }
362
+ return totalWrote;
363
+ }
364
+
365
+ /**
366
+ * Look up a dot/bracket path on a single item, e.g. "images.0" or
367
+ * "sizing.sizes.0.range". Returns undefined for missing branches.
368
+ * Used by fanout's per-item JSON-LD field mapper.
369
+ */
370
+ function getByPath(obj, path) {
371
+ if (obj == null || !path) return undefined;
372
+ const parts = String(path).split(".");
373
+ let acc = obj;
374
+ for (const p of parts) {
375
+ if (acc == null) return undefined;
376
+ acc = acc[p];
377
+ }
378
+ return acc;
379
+ }
380
+
381
+ /**
382
+ * Build a schema.org JSON-LD object for one fanout item. `fields`
383
+ * maps schema-key → dot-path on the item; `extra` is merged in
384
+ * verbatim (static fields like brand, priceCurrency, "@context").
385
+ */
386
+ function buildItemJsonLd(type, fields, extra, item) {
387
+ const out = { "@context": "https://schema.org", "@type": type };
388
+ for (const [key, fieldPath] of Object.entries(fields || {})) {
389
+ const v = getByPath(item, fieldPath);
390
+ if (v !== undefined && v !== null && v !== "") out[key] = v;
391
+ }
392
+ if (extra && typeof extra === "object") {
393
+ for (const [k, v] of Object.entries(extra)) {
394
+ if (v !== undefined) out[k] = v;
395
+ }
396
+ }
397
+ return out;
398
+ }
399
+
400
+ /** Resolve a dot-path with `[]` segments to an array of items. */
401
+ function resolveItemsPath(data, path) {
402
+ if (!path) return Array.isArray(data) ? data : [];
403
+ const tokens = path.split(/\.|(\[\])/).filter(Boolean);
404
+ let acc = data;
405
+ for (const tok of tokens) {
406
+ if (tok === "[]") {
407
+ if (!Array.isArray(acc)) return [];
408
+ continue;
409
+ }
410
+ if (Array.isArray(acc)) {
411
+ acc = acc.flatMap((x) => (x != null ? [x[tok]] : [])).filter((x) => x != null);
412
+ // After accessing a field through an array, the result is an
413
+ // array of values (possibly nested). Don't auto-flatten — the
414
+ // user can put another `[]` after if they want.
415
+ } else if (acc != null) {
416
+ acc = acc[tok];
417
+ } else {
418
+ return [];
419
+ }
420
+ }
421
+ return Array.isArray(acc) ? acc.flat(Infinity).filter((x) => x != null) : [];
422
+ }
423
+
424
+ /** Replace `{field}` in a template with item[field]. */
425
+ function interpolate(template, item) {
426
+ return template.replace(/\{([^}]+)\}/g, (_, key) => {
427
+ const v = key.split(".").reduce((acc, k) => (acc == null ? acc : acc[k]), item);
428
+ return v == null ? "" : String(v);
429
+ });
430
+ }
431
+
432
+ function escapeRegex(s) {
433
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
434
+ }
435
+
436
+ function escapeHtml(s) {
437
+ return String(s).replace(/[<>&"']/g, (c) =>
438
+ c === "<" ? "&lt;" : c === ">" ? "&gt;" : c === "&" ? "&amp;" : c === '"' ? "&quot;" : "&#39;",
439
+ );
440
+ }
441
+
165
442
  // ─── Page auto-discovery ────────────────────────────────────────
166
443
 
167
444
  /**
@@ -258,6 +535,16 @@ async function runPrerender(rawArgs) {
258
535
  );
259
536
  }
260
537
 
538
+ // Data-driven page fanout. When the config declares a `fanout`
539
+ // block (per-product detail pages, per-article blog entries, etc.),
540
+ // expand it BEFORE auto-discovery so the generated HTMLs are picked
541
+ // up alongside the hand-written ones. Subprocess child renders skip
542
+ // fanout — the parent already wrote the files and re-running inside
543
+ // each locale would just thrash them.
544
+ if (!process.env.NODALITY_SSG_LOCALE && Array.isArray(fileConfig.fanout)) {
545
+ runFanout(cwd, uploadDir, fileConfig.fanout);
546
+ }
547
+
261
548
  // Explicit `pages` from config wins over auto-discovery. The
262
549
  // discovery rules (pages/<base>.js, <base>.js) can't infer
263
550
  // irregular pairs like h7-nodality's `index.html → app.js`; for
@@ -441,6 +728,29 @@ ${body}
441
728
  }
442
729
  }
443
730
 
731
+ // ─── fanout subcommand (standalone) ────────────────────────────
732
+
733
+ /**
734
+ * Run the fanout expansion standalone (without prerender) — useful
735
+ * for debugging the generated files before kicking off a full SSG.
736
+ * Reads the `fanout` block from nodality.config.json.
737
+ */
738
+ async function runFanoutStandalone(rawArgs) {
739
+ const cwd = process.cwd();
740
+ const flags = parseFlags(rawArgs);
741
+ const fileConfig = loadConfigFile(cwd);
742
+ const uploadDir = path.resolve(cwd, flags.upload || fileConfig.uploadDir || "upload");
743
+
744
+ if (!Array.isArray(fileConfig.fanout) || fileConfig.fanout.length === 0) {
745
+ console.error(`[nodality] fanout: no \`fanout\` block in nodality.config.json`);
746
+ process.exit(1);
747
+ }
748
+ const wrote = runFanout(cwd, uploadDir, fileConfig.fanout);
749
+ if (wrote === 0) {
750
+ console.warn(`[nodality] fanout: 0 page(s) generated — check config & data`);
751
+ }
752
+ }
753
+
444
754
  // ─── Dispatch ──────────────────────────────────────────────────
445
755
 
446
756
  async function main() {
@@ -462,6 +772,8 @@ async function main() {
462
772
  await runPrerender(rest);
463
773
  } else if (command === "compile") {
464
774
  await runCompile(rest);
775
+ } else if (command === "fanout") {
776
+ await runFanoutStandalone(rest);
465
777
  } else {
466
778
  console.error(`[nodality] Unknown command: ${command}`);
467
779
  showUsage();
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/audio.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/base-2.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/base.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/box.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/button.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/cards.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/center.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/circle.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/code.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/custom.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/footer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/free.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/grid.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.166
2
+ * nodality v1.0.168
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */