@motionscript/layout-grid 0.0.0-stage → 0.1.0-alpha.3

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 (132) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +201 -0
  3. package/dist/browser/chunks/chunk-LEUMKC3A.js +2 -0
  4. package/dist/browser/chunks/chunk-LEUMKC3A.js.map +7 -0
  5. package/dist/browser/index.js +2 -0
  6. package/dist/browser/index.js.map +7 -0
  7. package/dist/browser/kit.js +2 -0
  8. package/dist/browser/kit.js.map +7 -0
  9. package/dist/browser/manifest.json +12 -0
  10. package/dist/engine.d.ts +13 -0
  11. package/dist/engine.d.ts.map +1 -0
  12. package/dist/engine.js +13 -0
  13. package/dist/engine.js.map +1 -0
  14. package/dist/index.d.ts +7 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +7 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/kit/index.d.ts +7 -0
  19. package/dist/kit/index.d.ts.map +1 -0
  20. package/dist/kit/index.js +7 -0
  21. package/dist/kit/index.js.map +1 -0
  22. package/dist/kit/item-effects.d.ts +37 -0
  23. package/dist/kit/item-effects.d.ts.map +1 -0
  24. package/dist/kit/item-effects.js +76 -0
  25. package/dist/kit/item-effects.js.map +1 -0
  26. package/dist/kit/layout-grid.d.ts +243 -0
  27. package/dist/kit/layout-grid.d.ts.map +1 -0
  28. package/dist/kit/layout-grid.js +542 -0
  29. package/dist/kit/layout-grid.js.map +1 -0
  30. package/dist/kit/order.d.ts +13 -0
  31. package/dist/kit/order.d.ts.map +1 -0
  32. package/dist/kit/order.js +43 -0
  33. package/dist/kit/order.js.map +1 -0
  34. package/dist/kit/random.d.ts +7 -0
  35. package/dist/kit/random.d.ts.map +1 -0
  36. package/dist/kit/random.js +29 -0
  37. package/dist/kit/random.js.map +1 -0
  38. package/dist/kit/slots/index.d.ts +22 -0
  39. package/dist/kit/slots/index.d.ts.map +1 -0
  40. package/dist/kit/slots/index.js +28 -0
  41. package/dist/kit/slots/index.js.map +1 -0
  42. package/dist/kit/slots/path-templates.d.ts +15 -0
  43. package/dist/kit/slots/path-templates.d.ts.map +1 -0
  44. package/dist/kit/slots/path-templates.js +113 -0
  45. package/dist/kit/slots/path-templates.js.map +1 -0
  46. package/dist/kit/slots/path.d.ts +45 -0
  47. package/dist/kit/slots/path.d.ts.map +1 -0
  48. package/dist/kit/slots/path.js +87 -0
  49. package/dist/kit/slots/path.js.map +1 -0
  50. package/dist/kit/slots/radial.d.ts +30 -0
  51. package/dist/kit/slots/radial.d.ts.map +1 -0
  52. package/dist/kit/slots/radial.js +45 -0
  53. package/dist/kit/slots/radial.js.map +1 -0
  54. package/dist/kit/slots/rectangular.d.ts +42 -0
  55. package/dist/kit/slots/rectangular.d.ts.map +1 -0
  56. package/dist/kit/slots/rectangular.js +79 -0
  57. package/dist/kit/slots/rectangular.js.map +1 -0
  58. package/dist/kit/slots/slot.d.ts +62 -0
  59. package/dist/kit/slots/slot.d.ts.map +1 -0
  60. package/dist/kit/slots/slot.js +41 -0
  61. package/dist/kit/slots/slot.js.map +1 -0
  62. package/dist/kit/slots/sphere.d.ts +36 -0
  63. package/dist/kit/slots/sphere.d.ts.map +1 -0
  64. package/dist/kit/slots/sphere.js +68 -0
  65. package/dist/kit/slots/sphere.js.map +1 -0
  66. package/dist/kit/stagger.d.ts +24 -0
  67. package/dist/kit/stagger.d.ts.map +1 -0
  68. package/dist/kit/stagger.js +90 -0
  69. package/dist/kit/stagger.js.map +1 -0
  70. package/dist/nodes.d.ts +15 -0
  71. package/dist/nodes.d.ts.map +1 -0
  72. package/dist/nodes.js +15 -0
  73. package/dist/nodes.js.map +1 -0
  74. package/dist/path-grid/index.d.ts +2 -0
  75. package/dist/path-grid/index.d.ts.map +1 -0
  76. package/dist/path-grid/index.js +2 -0
  77. package/dist/path-grid/index.js.map +1 -0
  78. package/dist/path-grid/path-grid.d.ts +82 -0
  79. package/dist/path-grid/path-grid.d.ts.map +1 -0
  80. package/dist/path-grid/path-grid.js +192 -0
  81. package/dist/path-grid/path-grid.js.map +1 -0
  82. package/dist/radial-grid/index.d.ts +2 -0
  83. package/dist/radial-grid/index.d.ts.map +1 -0
  84. package/dist/radial-grid/index.js +2 -0
  85. package/dist/radial-grid/index.js.map +1 -0
  86. package/dist/radial-grid/radial-grid.d.ts +51 -0
  87. package/dist/radial-grid/radial-grid.d.ts.map +1 -0
  88. package/dist/radial-grid/radial-grid.js +95 -0
  89. package/dist/radial-grid/radial-grid.js.map +1 -0
  90. package/dist/rectangular-grid/index.d.ts +2 -0
  91. package/dist/rectangular-grid/index.d.ts.map +1 -0
  92. package/dist/rectangular-grid/index.js +2 -0
  93. package/dist/rectangular-grid/index.js.map +1 -0
  94. package/dist/rectangular-grid/rectangular-grid.d.ts +79 -0
  95. package/dist/rectangular-grid/rectangular-grid.d.ts.map +1 -0
  96. package/dist/rectangular-grid/rectangular-grid.js +156 -0
  97. package/dist/rectangular-grid/rectangular-grid.js.map +1 -0
  98. package/dist/sphere-grid/index.d.ts +2 -0
  99. package/dist/sphere-grid/index.d.ts.map +1 -0
  100. package/dist/sphere-grid/index.js +2 -0
  101. package/dist/sphere-grid/index.js.map +1 -0
  102. package/dist/sphere-grid/sphere-grid.d.ts +68 -0
  103. package/dist/sphere-grid/sphere-grid.d.ts.map +1 -0
  104. package/dist/sphere-grid/sphere-grid.js +149 -0
  105. package/dist/sphere-grid/sphere-grid.js.map +1 -0
  106. package/package.json +70 -3
  107. package/registry.json +32 -0
  108. package/src/engine.ts +12 -0
  109. package/src/index.ts +6 -0
  110. package/src/kit/index.ts +6 -0
  111. package/src/kit/item-effects.ts +114 -0
  112. package/src/kit/layout-grid.ts +605 -0
  113. package/src/kit/order.ts +41 -0
  114. package/src/kit/random.ts +29 -0
  115. package/src/kit/slots/index.ts +39 -0
  116. package/src/kit/slots/path-templates.ts +120 -0
  117. package/src/kit/slots/path.ts +120 -0
  118. package/src/kit/slots/radial.ts +62 -0
  119. package/src/kit/slots/rectangular.ts +108 -0
  120. package/src/kit/slots/slot.ts +82 -0
  121. package/src/kit/slots/sphere.ts +94 -0
  122. package/src/kit/stagger.ts +96 -0
  123. package/src/nodes.ts +15 -0
  124. package/src/path-grid/index.ts +1 -0
  125. package/src/path-grid/path-grid.ts +213 -0
  126. package/src/radial-grid/index.ts +1 -0
  127. package/src/radial-grid/radial-grid.ts +90 -0
  128. package/src/rectangular-grid/index.ts +1 -0
  129. package/src/rectangular-grid/rectangular-grid.ts +160 -0
  130. package/src/sphere-grid/index.ts +1 -0
  131. package/src/sphere-grid/sphere-grid.ts +143 -0
  132. package/README.md +0 -4
@@ -0,0 +1,42 @@
1
+ import type { Size2D } from "@motionscript/sdk";
2
+ import { type Slot, type SlotInput } from "./slot.js";
3
+ export declare const FILL_DIRECTIONS: readonly ["row", "column"];
4
+ export type FillDirection = (typeof FILL_DIRECTIONS)[number];
5
+ /**
6
+ * How big a cell is: the largest child (`fit`), a size of its own (`fixed`), or
7
+ * an equal share of the grid's box (`fill`), which is also the room a child set
8
+ * to `fill` grows into.
9
+ */
10
+ export declare const CELL_MODES: readonly ["fit", "fixed", "fill"];
11
+ export type CellMode = (typeof CELL_MODES)[number];
12
+ /** Where a short last row (or column, filling by column) sits. */
13
+ export declare const LINE_ALIGNS: readonly ["start", "center", "end"];
14
+ export type LineAlign = (typeof LINE_ALIGNS)[number];
15
+ export interface RectangularArrangement {
16
+ kind: "rectangular";
17
+ columns: number;
18
+ fillDirection: FillDirection;
19
+ cellMode: CellMode;
20
+ cellWidth: number;
21
+ cellHeight: number;
22
+ columnGap: number;
23
+ rowGap: number;
24
+ lastLineAlign: LineAlign;
25
+ /** Share of a column's pitch every other row shifts right — `0.5` is a brick bond. */
26
+ rowOffset: number;
27
+ /** Share of a row's pitch every other column shifts down. */
28
+ columnOffset: number;
29
+ }
30
+ /** Rows and columns `count` items make, `columns` wide. */
31
+ export declare function rectangularShape(arrangement: RectangularArrangement, count: number): {
32
+ rows: number;
33
+ columns: number;
34
+ };
35
+ /** The cell every item is laid out in — what a `fill` child is measured against. */
36
+ export declare function rectangularCell(arrangement: RectangularArrangement, count: number, sizes: readonly Size2D[], box: Size2D): Size2D;
37
+ /**
38
+ * Items fill rows left to right, top to bottom (or columns top to bottom, left to
39
+ * right), and the whole block is centred on the grid.
40
+ */
41
+ export declare function rectangularSlots(arrangement: RectangularArrangement, input: SlotInput): Slot[];
42
+ //# sourceMappingURL=rectangular.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rectangular.d.ts","sourceRoot":"","sources":["../../../src/kit/slots/rectangular.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAiB,KAAK,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAA;AAEjE,eAAO,MAAM,eAAe,4BAA6B,CAAA;AACzD,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE5D;;;;GAIG;AACH,eAAO,MAAM,UAAU,mCAAoC,CAAA;AAC3D,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAElD,kEAAkE;AAClE,eAAO,MAAM,WAAW,qCAAsC,CAAA;AAC9D,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAEpD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,aAAa,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,aAAa,CAAA;IAC5B,QAAQ,EAAE,QAAQ,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,SAAS,CAAA;IACxB,sFAAsF;IACtF,SAAS,EAAE,MAAM,CAAA;IACjB,6DAA6D;IAC7D,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,sBAAsB,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAOtH;AAED,oFAAoF;AACpF,wBAAgB,eAAe,CAC7B,WAAW,EAAE,sBAAsB,EACnC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,GAAG,EAAE,MAAM,GACV,MAAM,CAaR;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,sBAAsB,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,EAAE,CAqC9F"}
@@ -0,0 +1,79 @@
1
+ import { largest, slot } from "./slot.js";
2
+ export const FILL_DIRECTIONS = ["row", "column"];
3
+ /**
4
+ * How big a cell is: the largest child (`fit`), a size of its own (`fixed`), or
5
+ * an equal share of the grid's box (`fill`), which is also the room a child set
6
+ * to `fill` grows into.
7
+ */
8
+ export const CELL_MODES = ["fit", "fixed", "fill"];
9
+ /** Where a short last row (or column, filling by column) sits. */
10
+ export const LINE_ALIGNS = ["start", "center", "end"];
11
+ /** Rows and columns `count` items make, `columns` wide. */
12
+ export function rectangularShape(arrangement, count) {
13
+ const columns = Math.max(1, Math.round(arrangement.columns));
14
+ const rows = Math.max(1, Math.ceil(count / columns));
15
+ // Filling by column, the rows are set by the count, and a column the items
16
+ // never reach would pull the block off centre.
17
+ if (arrangement.fillDirection === "column")
18
+ return { rows, columns: Math.max(1, Math.ceil(count / rows)) };
19
+ return { rows, columns };
20
+ }
21
+ /** The cell every item is laid out in — what a `fill` child is measured against. */
22
+ export function rectangularCell(arrangement, count, sizes, box) {
23
+ const { rows, columns } = rectangularShape(arrangement, count);
24
+ switch (arrangement.cellMode) {
25
+ case "fixed":
26
+ return { width: Math.max(0, arrangement.cellWidth), height: Math.max(0, arrangement.cellHeight) };
27
+ case "fill":
28
+ return {
29
+ width: Math.max(0, (box.width - arrangement.columnGap * (columns - 1)) / columns),
30
+ height: Math.max(0, (box.height - arrangement.rowGap * (rows - 1)) / rows),
31
+ };
32
+ default:
33
+ return largest(sizes);
34
+ }
35
+ }
36
+ /**
37
+ * Items fill rows left to right, top to bottom (or columns top to bottom, left to
38
+ * right), and the whole block is centred on the grid.
39
+ */
40
+ export function rectangularSlots(arrangement, input) {
41
+ const { count } = input;
42
+ if (count === 0)
43
+ return [];
44
+ const { rows, columns } = rectangularShape(arrangement, count);
45
+ const byColumn = arrangement.fillDirection === "column";
46
+ // Filling by column keeps the column count and lets the rows follow from it.
47
+ const lines = byColumn ? columns : rows;
48
+ const perLine = byColumn ? rows : columns;
49
+ const cell = rectangularCell(arrangement, count, input.sizes, input.box);
50
+ const pitchX = cell.width + arrangement.columnGap;
51
+ const pitchY = cell.height + arrangement.rowGap;
52
+ const lastLine = lines - 1;
53
+ const lastCount = count - lastLine * perLine;
54
+ const shortBy = perLine - lastCount;
55
+ const lead = arrangement.lastLineAlign === "center" ? shortBy / 2 : arrangement.lastLineAlign === "end" ? shortBy : 0;
56
+ // Shifting every other row by a share of a pitch moves the block's centre by
57
+ // half that share; taking it back keeps the block centred on the grid.
58
+ const shiftX = arrangement.rowOffset * pitchX;
59
+ const shiftY = arrangement.columnOffset * pitchY;
60
+ const slots = [];
61
+ for (let i = 0; i < count; i++) {
62
+ const line = Math.floor(i / perLine);
63
+ let along = i % perLine;
64
+ if (line === lastLine)
65
+ along += lead;
66
+ const row = byColumn ? along : line;
67
+ const column = byColumn ? line : along;
68
+ // A centred short line sits between whole cells; its parity is the cell it covers most.
69
+ const rowIndex = Math.round(row);
70
+ const columnIndex = Math.round(column);
71
+ const x = (column - (columns - 1) / 2) * pitchX
72
+ + (rowIndex % 2 === 1 ? shiftX : 0) - (rows > 1 ? shiftX / 2 : 0);
73
+ const y = ((rows - 1) / 2 - row) * pitchY
74
+ - (columnIndex % 2 === 1 ? shiftY : 0) + (columns > 1 ? shiftY / 2 : 0);
75
+ slots.push(slot(x, y, { kind: "rectangular", row: rowIndex, column: columnIndex }));
76
+ }
77
+ return slots;
78
+ }
79
+ //# sourceMappingURL=rectangular.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rectangular.js","sourceRoot":"","sources":["../../../src/kit/slots/rectangular.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Size2D } from \"@motionscript/sdk\"\n\nimport { largest, slot, type Slot, type SlotInput } from \"./slot\"\n\nexport const FILL_DIRECTIONS = [\"row\", \"column\"] as const\nexport type FillDirection = (typeof FILL_DIRECTIONS)[number]\n\n/**\n * How big a cell is: the largest child (`fit`), a size of its own (`fixed`), or\n * an equal share of the grid's box (`fill`), which is also the room a child set\n * to `fill` grows into.\n */\nexport const CELL_MODES = [\"fit\", \"fixed\", \"fill\"] as const\nexport type CellMode = (typeof CELL_MODES)[number]\n\n/** Where a short last row (or column, filling by column) sits. */\nexport const LINE_ALIGNS = [\"start\", \"center\", \"end\"] as const\nexport type LineAlign = (typeof LINE_ALIGNS)[number]\n\nexport interface RectangularArrangement {\n kind: \"rectangular\"\n columns: number\n fillDirection: FillDirection\n cellMode: CellMode\n cellWidth: number\n cellHeight: number\n columnGap: number\n rowGap: number\n lastLineAlign: LineAlign\n /** Share of a column's pitch every other row shifts right — `0.5` is a brick bond. */\n rowOffset: number\n /** Share of a row's pitch every other column shifts down. */\n columnOffset: number\n}\n\n/** Rows and columns `count` items make, `columns` wide. */\nexport function rectangularShape(arrangement: RectangularArrangement, count: number): { rows: number; columns: number } {\n const columns = Math.max(1, Math.round(arrangement.columns))\n const rows = Math.max(1, Math.ceil(count / columns))\n // Filling by column, the rows are set by the count, and a column the items\n // never reach would pull the block off centre.\n if (arrangement.fillDirection === \"column\") return { rows, columns: Math.max(1, Math.ceil(count / rows)) }\n return { rows, columns }\n}\n\n/** The cell every item is laid out in — what a `fill` child is measured against. */\nexport function rectangularCell(\n arrangement: RectangularArrangement,\n count: number,\n sizes: readonly Size2D[],\n box: Size2D,\n): Size2D {\n const { rows, columns } = rectangularShape(arrangement, count)\n switch (arrangement.cellMode) {\n case \"fixed\":\n return { width: Math.max(0, arrangement.cellWidth), height: Math.max(0, arrangement.cellHeight) }\n case \"fill\":\n return {\n width: Math.max(0, (box.width - arrangement.columnGap * (columns - 1)) / columns),\n height: Math.max(0, (box.height - arrangement.rowGap * (rows - 1)) / rows),\n }\n default:\n return largest(sizes)\n }\n}\n\n/**\n * Items fill rows left to right, top to bottom (or columns top to bottom, left to\n * right), and the whole block is centred on the grid.\n */\nexport function rectangularSlots(arrangement: RectangularArrangement, input: SlotInput): Slot[] {\n const { count } = input\n if (count === 0) return []\n const { rows, columns } = rectangularShape(arrangement, count)\n const byColumn = arrangement.fillDirection === \"column\"\n // Filling by column keeps the column count and lets the rows follow from it.\n const lines = byColumn ? columns : rows\n const perLine = byColumn ? rows : columns\n const cell = rectangularCell(arrangement, count, input.sizes, input.box)\n const pitchX = cell.width + arrangement.columnGap\n const pitchY = cell.height + arrangement.rowGap\n const lastLine = lines - 1\n const lastCount = count - lastLine * perLine\n const shortBy = perLine - lastCount\n const lead = arrangement.lastLineAlign === \"center\" ? shortBy / 2 : arrangement.lastLineAlign === \"end\" ? shortBy : 0\n // Shifting every other row by a share of a pitch moves the block's centre by\n // half that share; taking it back keeps the block centred on the grid.\n const shiftX = arrangement.rowOffset * pitchX\n const shiftY = arrangement.columnOffset * pitchY\n\n const slots: Slot[] = []\n for (let i = 0; i < count; i++) {\n const line = Math.floor(i / perLine)\n let along = i % perLine\n if (line === lastLine) along += lead\n const row = byColumn ? along : line\n const column = byColumn ? line : along\n // A centred short line sits between whole cells; its parity is the cell it covers most.\n const rowIndex = Math.round(row)\n const columnIndex = Math.round(column)\n const x = (column - (columns - 1) / 2) * pitchX\n + (rowIndex % 2 === 1 ? shiftX : 0) - (rows > 1 ? shiftX / 2 : 0)\n const y = ((rows - 1) / 2 - row) * pitchY\n - (columnIndex % 2 === 1 ? shiftY : 0) + (columns > 1 ? shiftY / 2 : 0)\n slots.push(slot(x, y, { kind: \"rectangular\", row: rowIndex, column: columnIndex }))\n }\n return slots\n}\n"]}
@@ -0,0 +1,62 @@
1
+ import type { Size2D } from "@motionscript/sdk";
2
+ /**
3
+ * One place in an arrangement, in the grid's own frame: centred on it, y-up,
4
+ * `depth` toward the viewer.
5
+ *
6
+ * Pure data, and the whole of what an arrangement produces — which is what lets
7
+ * a morph blend any two of them item by item, whatever shapes they came from.
8
+ */
9
+ export interface Slot {
10
+ x: number;
11
+ y: number;
12
+ depth: number;
13
+ /** Degrees clockwise. */
14
+ rotation: number;
15
+ /** Tilt, degrees, as a node's own `rotationX`/`rotationY`. */
16
+ rotationX: number;
17
+ rotationY: number;
18
+ scale: number;
19
+ opacity: number;
20
+ meta: SlotMeta;
21
+ }
22
+ /** What kind of place a slot is — the orderings a stagger can follow. */
23
+ export type SlotMeta = {
24
+ kind: "rectangular";
25
+ row: number;
26
+ column: number;
27
+ }
28
+ /** Degrees clockwise from the arrangement's first slot. */
29
+ | {
30
+ kind: "radial";
31
+ angle: number;
32
+ }
33
+ /** `0..1` along the part of the path in use. */
34
+ | {
35
+ kind: "path";
36
+ t: number;
37
+ }
38
+ /** `depth` is `-1` at the back of the sphere and `1` at the front. */
39
+ | {
40
+ kind: "sphere";
41
+ lat: number;
42
+ lon: number;
43
+ depth: number;
44
+ };
45
+ /** What an arrangement is laid out for. */
46
+ export interface SlotInput {
47
+ count: number;
48
+ /** Each item's measured size, in item order. */
49
+ sizes: readonly Size2D[];
50
+ /** The grid's content box. */
51
+ box: Size2D;
52
+ /** Scene seconds, for arrangements that move on their own. */
53
+ time: number;
54
+ }
55
+ export declare function slot(x: number, y: number, meta: SlotMeta): Slot;
56
+ /** `a` toward `b`. The meta switches halfway, since an ordering cannot be half one thing. */
57
+ export declare function blendSlot(a: Slot, b: Slot, t: number): Slot;
58
+ /** Push `s` away from (or pull it toward) the grid's centre. */
59
+ export declare function spreadSlot(s: Slot, spread: number): Slot;
60
+ /** Largest width and height among `sizes`. */
61
+ export declare function largest(sizes: readonly Size2D[]): Size2D;
62
+ //# sourceMappingURL=slot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot.d.ts","sourceRoot":"","sources":["../../../src/kit/slots/slot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C;;;;;;GAMG;AACH,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,QAAQ,CAAA;CACf;AAED,yEAAyE;AACzE,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACtD,2DAA2D;GACzD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACnC,gDAAgD;GAC9C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE;AAC7B,sEAAsE;GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/D,2CAA2C;AAC3C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAE/D;AAED,6FAA6F;AAC7F,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAe3D;AAED,gEAAgE;AAChE,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAGxD;AAED,8CAA8C;AAC9C,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAQxD"}
@@ -0,0 +1,41 @@
1
+ export function slot(x, y, meta) {
2
+ return { x, y, depth: 0, rotation: 0, rotationX: 0, rotationY: 0, scale: 1, opacity: 1, meta };
3
+ }
4
+ /** `a` toward `b`. The meta switches halfway, since an ordering cannot be half one thing. */
5
+ export function blendSlot(a, b, t) {
6
+ if (t <= 0)
7
+ return a;
8
+ if (t >= 1)
9
+ return b;
10
+ const mix = (p, q) => p + (q - p) * t;
11
+ return {
12
+ x: mix(a.x, b.x),
13
+ y: mix(a.y, b.y),
14
+ depth: mix(a.depth, b.depth),
15
+ rotation: mix(a.rotation, b.rotation),
16
+ rotationX: mix(a.rotationX, b.rotationX),
17
+ rotationY: mix(a.rotationY, b.rotationY),
18
+ scale: mix(a.scale, b.scale),
19
+ opacity: mix(a.opacity, b.opacity),
20
+ meta: t < 0.5 ? a.meta : b.meta,
21
+ };
22
+ }
23
+ /** Push `s` away from (or pull it toward) the grid's centre. */
24
+ export function spreadSlot(s, spread) {
25
+ if (spread === 1)
26
+ return s;
27
+ return { ...s, x: s.x * spread, y: s.y * spread, depth: s.depth * spread };
28
+ }
29
+ /** Largest width and height among `sizes`. */
30
+ export function largest(sizes) {
31
+ let width = 0;
32
+ let height = 0;
33
+ for (const size of sizes) {
34
+ if (size.width > width)
35
+ width = size.width;
36
+ if (size.height > height)
37
+ height = size.height;
38
+ }
39
+ return { width, height };
40
+ }
41
+ //# sourceMappingURL=slot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot.js","sourceRoot":"","sources":["../../../src/kit/slots/slot.ts"],"names":[],"mappings":"AA4CA,MAAM,UAAU,IAAI,CAAC,CAAS,EAAE,CAAS,EAAE,IAAc;IACvD,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAA;AAChG,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,SAAS,CAAC,CAAO,EAAE,CAAO,EAAE,CAAS;IACnD,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAA;IACpB,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAA;IACpB,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;IACrD,OAAO;QACL,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAChB,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAChB,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC;QAC5B,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC;QACrC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC;QACxC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC;QACxC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC;QAC5B,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC;QAClC,IAAI,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;KAChC,CAAA;AACH,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,UAAU,CAAC,CAAO,EAAE,MAAc;IAChD,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IAC1B,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,GAAG,MAAM,EAAE,CAAA;AAC5E,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,OAAO,CAAC,KAAwB;IAC9C,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,KAAK,GAAG,KAAK;YAAE,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;QAC1C,IAAI,IAAI,CAAC,MAAM,GAAG,MAAM;YAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;IAChD,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAC1B,CAAC","sourcesContent":["import type { Size2D } from \"@motionscript/sdk\"\n\n/**\n * One place in an arrangement, in the grid's own frame: centred on it, y-up,\n * `depth` toward the viewer.\n *\n * Pure data, and the whole of what an arrangement produces — which is what lets\n * a morph blend any two of them item by item, whatever shapes they came from.\n */\nexport interface Slot {\n x: number\n y: number\n depth: number\n /** Degrees clockwise. */\n rotation: number\n /** Tilt, degrees, as a node's own `rotationX`/`rotationY`. */\n rotationX: number\n rotationY: number\n scale: number\n opacity: number\n meta: SlotMeta\n}\n\n/** What kind of place a slot is — the orderings a stagger can follow. */\nexport type SlotMeta =\n | { kind: \"rectangular\"; row: number; column: number }\n /** Degrees clockwise from the arrangement's first slot. */\n | { kind: \"radial\"; angle: number }\n /** `0..1` along the part of the path in use. */\n | { kind: \"path\"; t: number }\n /** `depth` is `-1` at the back of the sphere and `1` at the front. */\n | { kind: \"sphere\"; lat: number; lon: number; depth: number }\n\n/** What an arrangement is laid out for. */\nexport interface SlotInput {\n count: number\n /** Each item's measured size, in item order. */\n sizes: readonly Size2D[]\n /** The grid's content box. */\n box: Size2D\n /** Scene seconds, for arrangements that move on their own. */\n time: number\n}\n\nexport function slot(x: number, y: number, meta: SlotMeta): Slot {\n return { x, y, depth: 0, rotation: 0, rotationX: 0, rotationY: 0, scale: 1, opacity: 1, meta }\n}\n\n/** `a` toward `b`. The meta switches halfway, since an ordering cannot be half one thing. */\nexport function blendSlot(a: Slot, b: Slot, t: number): Slot {\n if (t <= 0) return a\n if (t >= 1) return b\n const mix = (p: number, q: number) => p + (q - p) * t\n return {\n x: mix(a.x, b.x),\n y: mix(a.y, b.y),\n depth: mix(a.depth, b.depth),\n rotation: mix(a.rotation, b.rotation),\n rotationX: mix(a.rotationX, b.rotationX),\n rotationY: mix(a.rotationY, b.rotationY),\n scale: mix(a.scale, b.scale),\n opacity: mix(a.opacity, b.opacity),\n meta: t < 0.5 ? a.meta : b.meta,\n }\n}\n\n/** Push `s` away from (or pull it toward) the grid's centre. */\nexport function spreadSlot(s: Slot, spread: number): Slot {\n if (spread === 1) return s\n return { ...s, x: s.x * spread, y: s.y * spread, depth: s.depth * spread }\n}\n\n/** Largest width and height among `sizes`. */\nexport function largest(sizes: readonly Size2D[]): Size2D {\n let width = 0\n let height = 0\n for (const size of sizes) {\n if (size.width > width) width = size.width\n if (size.height > height) height = size.height\n }\n return { width, height }\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import { type Slot, type SlotInput } from "./slot.js";
2
+ /** `outward` lays each item on the sphere's surface; `billboard` keeps every item facing the viewer. */
3
+ export declare const SPHERE_FACINGS: readonly ["outward", "billboard"];
4
+ export type SphereFacing = (typeof SPHERE_FACINGS)[number];
5
+ export declare const SPHERE_AXES: readonly ["x", "y", "z"];
6
+ export type SphereAxis = (typeof SPHERE_AXES)[number];
7
+ export interface SphereArrangement {
8
+ kind: "sphere";
9
+ radius: number;
10
+ /** Turn about the vertical axis, degrees — positive carries the front to the right. */
11
+ yaw: number;
12
+ /** Turn about the horizontal axis, degrees — positive brings the top toward the viewer. */
13
+ pitch: number;
14
+ /** Turn about the view axis, degrees clockwise. */
15
+ roll: number;
16
+ facing: SphereFacing;
17
+ /** The camera's distance from the sphere's centre plane, px — smaller is more dramatic. */
18
+ focalLength: number;
19
+ /** How much the back half fades, `0..1`. */
20
+ depthFade: number;
21
+ /** Hide the items whose faces point away from the viewer. */
22
+ cullBackfaces: boolean;
23
+ /** Degrees per second of yaw the sphere turns on its own. */
24
+ spinSpeed: number;
25
+ }
26
+ /** Point `k` of `count` on the unit sphere — a Fibonacci lattice, as even as `count` points get. */
27
+ export declare function fibonacciPoint(k: number, count: number): [number, number, number];
28
+ /** `p` turned by yaw, then pitch, then roll — y-up, z toward the viewer. */
29
+ export declare function orient(p: readonly [number, number, number], yaw: number, pitch: number, roll: number): [number, number, number];
30
+ export declare function sphereSlots(arrangement: SphereArrangement, input: SlotInput): Slot[];
31
+ /** The yaw and pitch that bring point `index` of `count` round to face the viewer. */
32
+ export declare function sphereFaceForward(count: number, index: number): {
33
+ yaw: number;
34
+ pitch: number;
35
+ };
36
+ //# sourceMappingURL=sphere.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sphere.d.ts","sourceRoot":"","sources":["../../../src/kit/slots/sphere.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAA;AAExD,wGAAwG;AACxG,eAAO,MAAM,cAAc,mCAAoC,CAAA;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAE1D,eAAO,MAAM,WAAW,0BAA2B,CAAA;AACnD,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAErD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAA;IACX,2FAA2F;IAC3F,KAAK,EAAE,MAAM,CAAA;IACb,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,YAAY,CAAA;IACpB,2FAA2F;IAC3F,WAAW,EAAE,MAAM,CAAA;IACnB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,6DAA6D;IAC7D,aAAa,EAAE,OAAO,CAAA;IACtB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAA;CAClB;AAKD,oGAAoG;AACpG,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAMjF;AAED,4EAA4E;AAC5E,wBAAgB,MAAM,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAQ/H;AAED,wBAAgB,WAAW,CAAC,WAAW,EAAE,iBAAiB,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,EAAE,CA0BpF;AAQD,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAM9F"}
@@ -0,0 +1,68 @@
1
+ import { slot } from "./slot.js";
2
+ /** `outward` lays each item on the sphere's surface; `billboard` keeps every item facing the viewer. */
3
+ export const SPHERE_FACINGS = ["outward", "billboard"];
4
+ export const SPHERE_AXES = ["x", "y", "z"];
5
+ const GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));
6
+ const DEG = 180 / Math.PI;
7
+ /** Point `k` of `count` on the unit sphere — a Fibonacci lattice, as even as `count` points get. */
8
+ export function fibonacciPoint(k, count) {
9
+ if (count <= 1)
10
+ return [0, 0, 1];
11
+ const y = 1 - (2 * (k + 0.5)) / count;
12
+ const r = Math.sqrt(Math.max(0, 1 - y * y));
13
+ const theta = k * GOLDEN_ANGLE;
14
+ return [Math.cos(theta) * r, y, Math.sin(theta) * r];
15
+ }
16
+ /** `p` turned by yaw, then pitch, then roll — y-up, z toward the viewer. */
17
+ export function orient(p, yaw, pitch, roll) {
18
+ const [x0, y0, z0] = p;
19
+ const ya = yaw / DEG, pa = pitch / DEG, ra = roll / DEG;
20
+ const x1 = x0 * Math.cos(ya) + z0 * Math.sin(ya);
21
+ const z1 = -x0 * Math.sin(ya) + z0 * Math.cos(ya);
22
+ const y2 = y0 * Math.cos(pa) - z1 * Math.sin(pa);
23
+ const z2 = y0 * Math.sin(pa) + z1 * Math.cos(pa);
24
+ return [x1 * Math.cos(ra) + y2 * Math.sin(ra), -x1 * Math.sin(ra) + y2 * Math.cos(ra), z2];
25
+ }
26
+ export function sphereSlots(arrangement, input) {
27
+ const { count } = input;
28
+ const radius = arrangement.radius;
29
+ const yaw = arrangement.yaw + arrangement.spinSpeed * input.time;
30
+ const outward = arrangement.facing === "outward";
31
+ const slots = [];
32
+ for (let k = 0; k < count; k++) {
33
+ const q = fibonacciPoint(k, count);
34
+ const [nx, ny, nz] = orient(q, yaw, arrangement.pitch, arrangement.roll);
35
+ const s = slot(nx * radius, ny * radius, {
36
+ kind: "sphere",
37
+ lat: Math.asin(Math.max(-1, Math.min(1, q[1]))) * DEG,
38
+ lon: Math.atan2(q[2], q[0]) * DEG,
39
+ depth: nz,
40
+ });
41
+ s.depth = nz * radius;
42
+ if (outward) {
43
+ // The tilt whose `Rx · Ry` turns a face's normal onto the surface normal.
44
+ s.rotationY = Math.asin(Math.max(-1, Math.min(1, nx))) * DEG;
45
+ s.rotationX = Math.atan2(ny, nz) * DEG;
46
+ }
47
+ s.opacity = 1 - arrangement.depthFade * (1 - (nz + 1) / 2);
48
+ if (outward && arrangement.cullBackfaces && facesAway(s, [nx, ny, nz], arrangement.focalLength))
49
+ s.opacity = 0;
50
+ slots.push(s);
51
+ }
52
+ return slots;
53
+ }
54
+ /** Whether an outward face at `s` shows its back to a viewer `focal` px in front of the centre. */
55
+ function facesAway(s, normal, focal) {
56
+ if (focal <= 0)
57
+ return normal[2] < 0;
58
+ return normal[0] * -s.x + normal[1] * -s.y + normal[2] * (focal - s.depth) < 0;
59
+ }
60
+ /** The yaw and pitch that bring point `index` of `count` round to face the viewer. */
61
+ export function sphereFaceForward(count, index) {
62
+ const [x, y, z] = fibonacciPoint(index, count);
63
+ return {
64
+ yaw: -Math.atan2(x, z) * DEG,
65
+ pitch: Math.atan2(y, Math.hypot(x, z)) * DEG,
66
+ };
67
+ }
68
+ //# sourceMappingURL=sphere.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sphere.js","sourceRoot":"","sources":["../../../src/kit/slots/sphere.ts"],"names":[],"mappings":"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","sourcesContent":["import { slot, type Slot, type SlotInput } from \"./slot\"\n\n/** `outward` lays each item on the sphere's surface; `billboard` keeps every item facing the viewer. */\nexport const SPHERE_FACINGS = [\"outward\", \"billboard\"] as const\nexport type SphereFacing = (typeof SPHERE_FACINGS)[number]\n\nexport const SPHERE_AXES = [\"x\", \"y\", \"z\"] as const\nexport type SphereAxis = (typeof SPHERE_AXES)[number]\n\nexport interface SphereArrangement {\n kind: \"sphere\"\n radius: number\n /** Turn about the vertical axis, degrees — positive carries the front to the right. */\n yaw: number\n /** Turn about the horizontal axis, degrees — positive brings the top toward the viewer. */\n pitch: number\n /** Turn about the view axis, degrees clockwise. */\n roll: number\n facing: SphereFacing\n /** The camera's distance from the sphere's centre plane, px — smaller is more dramatic. */\n focalLength: number\n /** How much the back half fades, `0..1`. */\n depthFade: number\n /** Hide the items whose faces point away from the viewer. */\n cullBackfaces: boolean\n /** Degrees per second of yaw the sphere turns on its own. */\n spinSpeed: number\n}\n\nconst GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5))\nconst DEG = 180 / Math.PI\n\n/** Point `k` of `count` on the unit sphere — a Fibonacci lattice, as even as `count` points get. */\nexport function fibonacciPoint(k: number, count: number): [number, number, number] {\n if (count <= 1) return [0, 0, 1]\n const y = 1 - (2 * (k + 0.5)) / count\n const r = Math.sqrt(Math.max(0, 1 - y * y))\n const theta = k * GOLDEN_ANGLE\n return [Math.cos(theta) * r, y, Math.sin(theta) * r]\n}\n\n/** `p` turned by yaw, then pitch, then roll — y-up, z toward the viewer. */\nexport function orient(p: readonly [number, number, number], yaw: number, pitch: number, roll: number): [number, number, number] {\n const [x0, y0, z0] = p\n const ya = yaw / DEG, pa = pitch / DEG, ra = roll / DEG\n const x1 = x0 * Math.cos(ya) + z0 * Math.sin(ya)\n const z1 = -x0 * Math.sin(ya) + z0 * Math.cos(ya)\n const y2 = y0 * Math.cos(pa) - z1 * Math.sin(pa)\n const z2 = y0 * Math.sin(pa) + z1 * Math.cos(pa)\n return [x1 * Math.cos(ra) + y2 * Math.sin(ra), -x1 * Math.sin(ra) + y2 * Math.cos(ra), z2]\n}\n\nexport function sphereSlots(arrangement: SphereArrangement, input: SlotInput): Slot[] {\n const { count } = input\n const radius = arrangement.radius\n const yaw = arrangement.yaw + arrangement.spinSpeed * input.time\n const outward = arrangement.facing === \"outward\"\n const slots: Slot[] = []\n for (let k = 0; k < count; k++) {\n const q = fibonacciPoint(k, count)\n const [nx, ny, nz] = orient(q, yaw, arrangement.pitch, arrangement.roll)\n const s = slot(nx * radius, ny * radius, {\n kind: \"sphere\",\n lat: Math.asin(Math.max(-1, Math.min(1, q[1]))) * DEG,\n lon: Math.atan2(q[2], q[0]) * DEG,\n depth: nz,\n })\n s.depth = nz * radius\n if (outward) {\n // The tilt whose `Rx · Ry` turns a face's normal onto the surface normal.\n s.rotationY = Math.asin(Math.max(-1, Math.min(1, nx))) * DEG\n s.rotationX = Math.atan2(ny, nz) * DEG\n }\n s.opacity = 1 - arrangement.depthFade * (1 - (nz + 1) / 2)\n if (outward && arrangement.cullBackfaces && facesAway(s, [nx, ny, nz], arrangement.focalLength)) s.opacity = 0\n slots.push(s)\n }\n return slots\n}\n\n/** Whether an outward face at `s` shows its back to a viewer `focal` px in front of the centre. */\nfunction facesAway(s: Slot, normal: readonly [number, number, number], focal: number): boolean {\n if (focal <= 0) return normal[2] < 0\n return normal[0] * -s.x + normal[1] * -s.y + normal[2] * (focal - s.depth) < 0\n}\n\n/** The yaw and pitch that bring point `index` of `count` round to face the viewer. */\nexport function sphereFaceForward(count: number, index: number): { yaw: number; pitch: number } {\n const [x, y, z] = fibonacciPoint(index, count)\n return {\n yaw: -Math.atan2(x, z) * DEG,\n pitch: Math.atan2(y, Math.hypot(x, z)) * DEG,\n }\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import type { Slot } from "./slots/index.js";
2
+ /**
3
+ * The order a per-item effect sweeps through a grid's items. The first five
4
+ * work on any grid; the rest follow one arrangement's own geometry — rows and
5
+ * columns of a rectangular grid (or top-to-bottom and left-to-right of any
6
+ * other), the way round a ring, the way along a path, front to back of a sphere
7
+ * — and fall back to item order where the grid has no such thing.
8
+ */
9
+ export declare const STAGGER_ORDERS: readonly ["index", "reverse", "random", "centerOut", "edgesIn", "row", "column", "angle", "path", "depth"];
10
+ export type StaggerOrder = (typeof STAGGER_ORDERS)[number];
11
+ /**
12
+ * Each item's start, `0..1`, as a share of the stagger: `0` goes first, `1`
13
+ * last, and items that tie go together — a ring revealed `centerOut` from its
14
+ * centre appears all at once, which is what that means.
15
+ */
16
+ export declare function staggerRanks(order: StaggerOrder, slots: readonly Slot[], seed: number): number[];
17
+ /**
18
+ * Item progress at command time `t`: each item runs over the same length of
19
+ * time, starting `rank · stagger` in. The stagger lives inside the command's
20
+ * duration, so a one-second reveal takes one second for four items or four
21
+ * hundred.
22
+ */
23
+ export declare function staggerProgress(rank: number, t: number, stagger: number): number;
24
+ //# sourceMappingURL=stagger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stagger.d.ts","sourceRoot":"","sources":["../../src/kit/stagger.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAEnC;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,4GAWjB,CAAA;AACV,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAK1D;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAYhG;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAIhF"}
@@ -0,0 +1,90 @@
1
+ import { shuffled } from "./random.js";
2
+ /**
3
+ * The order a per-item effect sweeps through a grid's items. The first five
4
+ * work on any grid; the rest follow one arrangement's own geometry — rows and
5
+ * columns of a rectangular grid (or top-to-bottom and left-to-right of any
6
+ * other), the way round a ring, the way along a path, front to back of a sphere
7
+ * — and fall back to item order where the grid has no such thing.
8
+ */
9
+ export const STAGGER_ORDERS = [
10
+ "index",
11
+ "reverse",
12
+ "random",
13
+ "centerOut",
14
+ "edgesIn",
15
+ "row",
16
+ "column",
17
+ "angle",
18
+ "path",
19
+ "depth",
20
+ ];
21
+ /** The most of a command's length the offsets may take; the rest is each item's own motion. */
22
+ const MAX_STAGGER = 0.95;
23
+ /**
24
+ * Each item's start, `0..1`, as a share of the stagger: `0` goes first, `1`
25
+ * last, and items that tie go together — a ring revealed `centerOut` from its
26
+ * centre appears all at once, which is what that means.
27
+ */
28
+ export function staggerRanks(order, slots, seed) {
29
+ const n = slots.length;
30
+ const keys = keysOf(order, slots, seed);
31
+ let min = Infinity;
32
+ let max = -Infinity;
33
+ for (const key of keys) {
34
+ if (key < min)
35
+ min = key;
36
+ if (key > max)
37
+ max = key;
38
+ }
39
+ const range = max - min;
40
+ if (!(range > 1e-9))
41
+ return new Array(n).fill(0);
42
+ return keys.map((key) => (key - min) / range);
43
+ }
44
+ /**
45
+ * Item progress at command time `t`: each item runs over the same length of
46
+ * time, starting `rank · stagger` in. The stagger lives inside the command's
47
+ * duration, so a one-second reveal takes one second for four items or four
48
+ * hundred.
49
+ */
50
+ export function staggerProgress(rank, t, stagger) {
51
+ const s = Math.max(0, Math.min(MAX_STAGGER, stagger));
52
+ const p = s === 0 ? t : (t - rank * s) / (1 - s);
53
+ return p < 0 ? 0 : p > 1 ? 1 : p;
54
+ }
55
+ function keysOf(order, slots, seed) {
56
+ switch (order) {
57
+ case "reverse":
58
+ return slots.map((_, i) => -i);
59
+ case "random": {
60
+ const sequence = shuffled(slots.map((_, i) => i), seed);
61
+ const keys = new Array(slots.length);
62
+ sequence.forEach((item, rank) => {
63
+ keys[item] = rank;
64
+ });
65
+ return keys;
66
+ }
67
+ case "centerOut":
68
+ return slots.map((s) => Math.round(Math.hypot(s.x, s.y, s.depth) * 1000) / 1000);
69
+ case "edgesIn":
70
+ return slots.map((s) => -Math.round(Math.hypot(s.x, s.y, s.depth) * 1000) / 1000);
71
+ case "row":
72
+ return slots.map((s) => (s.meta.kind === "rectangular" ? s.meta.row : -Math.round(s.y)));
73
+ case "column":
74
+ return slots.map((s) => (s.meta.kind === "rectangular" ? s.meta.column : Math.round(s.x)));
75
+ case "angle":
76
+ return slots.map((s, i) => s.meta.kind === "radial" ? s.meta.angle : s.meta.kind === "rectangular" || s.meta.kind === "sphere" ? clockAngle(s) : i);
77
+ case "path":
78
+ return slots.map((s, i) => (s.meta.kind === "path" ? s.meta.t : i));
79
+ case "depth":
80
+ return slots.map((s, i) => (s.meta.kind === "sphere" ? -s.meta.depth : i));
81
+ default:
82
+ return slots.map((_, i) => i);
83
+ }
84
+ }
85
+ /** Degrees clockwise from twelve o'clock to where `s` sits. */
86
+ function clockAngle(s) {
87
+ const angle = (Math.atan2(s.x, s.y) * 180) / Math.PI;
88
+ return angle < 0 ? angle + 360 : angle;
89
+ }
90
+ //# sourceMappingURL=stagger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stagger.js","sourceRoot":"","sources":["../../src/kit/stagger.ts"],"names":[],"mappings":"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","sourcesContent":["import { shuffled } from \"./random\"\nimport type { Slot } from \"./slots\"\n\n/**\n * The order a per-item effect sweeps through a grid's items. The first five\n * work on any grid; the rest follow one arrangement's own geometry — rows and\n * columns of a rectangular grid (or top-to-bottom and left-to-right of any\n * other), the way round a ring, the way along a path, front to back of a sphere\n * — and fall back to item order where the grid has no such thing.\n */\nexport const STAGGER_ORDERS = [\n \"index\",\n \"reverse\",\n \"random\",\n \"centerOut\",\n \"edgesIn\",\n \"row\",\n \"column\",\n \"angle\",\n \"path\",\n \"depth\",\n] as const\nexport type StaggerOrder = (typeof STAGGER_ORDERS)[number]\n\n/** The most of a command's length the offsets may take; the rest is each item's own motion. */\nconst MAX_STAGGER = 0.95\n\n/**\n * Each item's start, `0..1`, as a share of the stagger: `0` goes first, `1`\n * last, and items that tie go together — a ring revealed `centerOut` from its\n * centre appears all at once, which is what that means.\n */\nexport function staggerRanks(order: StaggerOrder, slots: readonly Slot[], seed: number): number[] {\n const n = slots.length\n const keys = keysOf(order, slots, seed)\n let min = Infinity\n let max = -Infinity\n for (const key of keys) {\n if (key < min) min = key\n if (key > max) max = key\n }\n const range = max - min\n if (!(range > 1e-9)) return new Array<number>(n).fill(0)\n return keys.map((key) => (key - min) / range)\n}\n\n/**\n * Item progress at command time `t`: each item runs over the same length of\n * time, starting `rank · stagger` in. The stagger lives inside the command's\n * duration, so a one-second reveal takes one second for four items or four\n * hundred.\n */\nexport function staggerProgress(rank: number, t: number, stagger: number): number {\n const s = Math.max(0, Math.min(MAX_STAGGER, stagger))\n const p = s === 0 ? t : (t - rank * s) / (1 - s)\n return p < 0 ? 0 : p > 1 ? 1 : p\n}\n\nfunction keysOf(order: StaggerOrder, slots: readonly Slot[], seed: number): number[] {\n switch (order) {\n case \"reverse\":\n return slots.map((_, i) => -i)\n case \"random\": {\n const sequence = shuffled(slots.map((_, i) => i), seed)\n const keys = new Array<number>(slots.length)\n sequence.forEach((item, rank) => {\n keys[item] = rank\n })\n return keys\n }\n case \"centerOut\":\n return slots.map((s) => Math.round(Math.hypot(s.x, s.y, s.depth) * 1000) / 1000)\n case \"edgesIn\":\n return slots.map((s) => -Math.round(Math.hypot(s.x, s.y, s.depth) * 1000) / 1000)\n case \"row\":\n return slots.map((s) => (s.meta.kind === \"rectangular\" ? s.meta.row : -Math.round(s.y)))\n case \"column\":\n return slots.map((s) => (s.meta.kind === \"rectangular\" ? s.meta.column : Math.round(s.x)))\n case \"angle\":\n return slots.map((s, i) =>\n s.meta.kind === \"radial\" ? s.meta.angle : s.meta.kind === \"rectangular\" || s.meta.kind === \"sphere\" ? clockAngle(s) : i\n )\n case \"path\":\n return slots.map((s, i) => (s.meta.kind === \"path\" ? s.meta.t : i))\n case \"depth\":\n return slots.map((s, i) => (s.meta.kind === \"sphere\" ? -s.meta.depth : i))\n default:\n return slots.map((_, i) => i)\n }\n}\n\n/** Degrees clockwise from twelve o'clock to where `s` sits. */\nfunction clockAngle(s: Slot): number {\n const angle = (Math.atan2(s.x, s.y) * 180) / Math.PI\n return angle < 0 ? angle + 360 : angle\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { PathGrid } from "./path-grid/index.js";
2
+ import { RadialGrid } from "./radial-grid/index.js";
3
+ import { RectangularGrid } from "./rectangular-grid/index.js";
4
+ import { SphereGrid } from "./sphere-grid/index.js";
5
+ /**
6
+ * Every node type this package publishes.
7
+ *
8
+ * A host registers these by handing the array to an engine —
9
+ * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`
10
+ * key. Listing class **values** is also what survives bundling: this package
11
+ * declares `sideEffects: false`, and a document names a node type by string, so
12
+ * an import kept only for its side effect is exactly what gets shaken out.
13
+ */
14
+ export declare const NODES: (typeof RectangularGrid | typeof RadialGrid | typeof PathGrid | typeof SphereGrid)[];
15
+ //# sourceMappingURL=nodes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE1C;;;;;;;;GAQG;AACH,eAAO,MAAM,KAAK,sFAAsD,CAAA"}
package/dist/nodes.js ADDED
@@ -0,0 +1,15 @@
1
+ import { PathGrid } from "./path-grid/index.js";
2
+ import { RadialGrid } from "./radial-grid/index.js";
3
+ import { RectangularGrid } from "./rectangular-grid/index.js";
4
+ import { SphereGrid } from "./sphere-grid/index.js";
5
+ /**
6
+ * Every node type this package publishes.
7
+ *
8
+ * A host registers these by handing the array to an engine —
9
+ * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`
10
+ * key. Listing class **values** is also what survives bundling: this package
11
+ * declares `sideEffects: false`, and a document names a node type by string, so
12
+ * an import kept only for its side effect is exactly what gets shaken out.
13
+ */
14
+ export const NODES = [RectangularGrid, RadialGrid, PathGrid, SphereGrid];
15
+ //# sourceMappingURL=nodes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodes.js","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE1C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,eAAe,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAA","sourcesContent":["import { PathGrid } from \"./path-grid\"\nimport { RadialGrid } from \"./radial-grid\"\nimport { RectangularGrid } from \"./rectangular-grid\"\nimport { SphereGrid } from \"./sphere-grid\"\n\n/**\n * Every node type this package publishes.\n *\n * A host registers these by handing the array to an engine —\n * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`\n * key. Listing class **values** is also what survives bundling: this package\n * declares `sideEffects: false`, and a document names a node type by string, so\n * an import kept only for its side effect is exactly what gets shaken out.\n */\nexport const NODES = [RectangularGrid, RadialGrid, PathGrid, SphereGrid]\n"]}
@@ -0,0 +1,2 @@
1
+ export * from "./path-grid.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/path-grid/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from "./path-grid.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/path-grid/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA","sourcesContent":["export * from \"./path-grid\"\n"]}