@anvpx/motion-scenes 0.0.0-stage → 0.1.1

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 (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -2
  3. package/dist/catalog.d.ts +17 -0
  4. package/dist/catalog.js +51 -0
  5. package/dist/custom.d.ts +86 -0
  6. package/dist/custom.js +176 -0
  7. package/dist/define-scene.d.ts +31 -0
  8. package/dist/define-scene.js +128 -0
  9. package/dist/html.d.ts +5 -0
  10. package/dist/html.js +14 -0
  11. package/dist/index.d.ts +11 -0
  12. package/dist/index.js +11 -0
  13. package/dist/position.d.ts +21 -0
  14. package/dist/position.js +16 -0
  15. package/dist/registry.d.ts +4 -0
  16. package/dist/registry.js +68 -0
  17. package/dist/runtime/compose.d.ts +21 -0
  18. package/dist/runtime/compose.js +43 -0
  19. package/dist/runtime/easing.d.ts +14 -0
  20. package/dist/runtime/easing.js +45 -0
  21. package/dist/runtime/mount.d.ts +50 -0
  22. package/dist/runtime/mount.js +153 -0
  23. package/dist/runtime/timeline.d.ts +147 -0
  24. package/dist/runtime/timeline.js +199 -0
  25. package/dist/runtime-files.d.ts +4 -0
  26. package/dist/runtime-files.js +18 -0
  27. package/dist/scene-contract.d.ts +7 -0
  28. package/dist/scene-contract.js +104 -0
  29. package/dist/scene3d/post.d.ts +12 -0
  30. package/dist/scene3d/post.js +16 -0
  31. package/dist/scene3d/reflector.d.ts +9 -0
  32. package/dist/scene3d/reflector.js +17 -0
  33. package/dist/scene3d/textures.d.ts +9 -0
  34. package/dist/scene3d/textures.js +11 -0
  35. package/dist/scene3d/volumetric.d.ts +9 -0
  36. package/dist/scene3d/volumetric.js +16 -0
  37. package/dist/scenes/bug/index.d.ts +1 -0
  38. package/dist/scenes/bug/index.js +45 -0
  39. package/dist/scenes/bug/scene.d.ts +2 -0
  40. package/dist/scenes/bug/scene.js +6 -0
  41. package/dist/scenes/bug/style.css +30 -0
  42. package/dist/scenes/bullets/index.d.ts +1 -0
  43. package/dist/scenes/bullets/index.js +128 -0
  44. package/dist/scenes/bullets/scene.d.ts +2 -0
  45. package/dist/scenes/bullets/scene.js +6 -0
  46. package/dist/scenes/bullets/style.css +91 -0
  47. package/dist/scenes/callout/index.d.ts +9 -0
  48. package/dist/scenes/callout/index.js +95 -0
  49. package/dist/scenes/callout/scene.d.ts +2 -0
  50. package/dist/scenes/callout/scene.js +43 -0
  51. package/dist/scenes/callout/style.css +97 -0
  52. package/dist/scenes/caption/index.d.ts +1 -0
  53. package/dist/scenes/caption/index.js +72 -0
  54. package/dist/scenes/caption/scene.d.ts +11 -0
  55. package/dist/scenes/caption/scene.js +33 -0
  56. package/dist/scenes/caption/style.css +33 -0
  57. package/dist/scenes/chapter/index.d.ts +1 -0
  58. package/dist/scenes/chapter/index.js +29 -0
  59. package/dist/scenes/chapter/scene.d.ts +2 -0
  60. package/dist/scenes/chapter/scene.js +6 -0
  61. package/dist/scenes/chapter/style.css +25 -0
  62. package/dist/scenes/chart/index.d.ts +1 -0
  63. package/dist/scenes/chart/index.js +24 -0
  64. package/dist/scenes/chart/render.d.ts +4 -0
  65. package/dist/scenes/chart/render.js +257 -0
  66. package/dist/scenes/chart/scale.d.ts +11 -0
  67. package/dist/scenes/chart/scale.js +53 -0
  68. package/dist/scenes/chart/scene.d.ts +2 -0
  69. package/dist/scenes/chart/scene.js +73 -0
  70. package/dist/scenes/chart/schema.d.ts +36 -0
  71. package/dist/scenes/chart/schema.js +109 -0
  72. package/dist/scenes/chart/style.css +425 -0
  73. package/dist/scenes/code/index.d.ts +1 -0
  74. package/dist/scenes/code/index.js +109 -0
  75. package/dist/scenes/code/scene.d.ts +2 -0
  76. package/dist/scenes/code/scene.js +108 -0
  77. package/dist/scenes/code/style.css +155 -0
  78. package/dist/scenes/code/tokenize.d.ts +8 -0
  79. package/dist/scenes/code/tokenize.js +145 -0
  80. package/dist/scenes/compare/index.d.ts +1 -0
  81. package/dist/scenes/compare/index.js +128 -0
  82. package/dist/scenes/compare/scene.d.ts +2 -0
  83. package/dist/scenes/compare/scene.js +58 -0
  84. package/dist/scenes/compare/style.css +187 -0
  85. package/dist/scenes/countdown/index.d.ts +1 -0
  86. package/dist/scenes/countdown/index.js +46 -0
  87. package/dist/scenes/countdown/scene.d.ts +2 -0
  88. package/dist/scenes/countdown/scene.js +21 -0
  89. package/dist/scenes/countdown/style.css +32 -0
  90. package/dist/scenes/flow/index.d.ts +1 -0
  91. package/dist/scenes/flow/index.js +66 -0
  92. package/dist/scenes/flow/inspect.d.ts +26 -0
  93. package/dist/scenes/flow/inspect.js +25 -0
  94. package/dist/scenes/flow/layout.d.ts +83 -0
  95. package/dist/scenes/flow/layout.js +222 -0
  96. package/dist/scenes/flow/measure.d.ts +15 -0
  97. package/dist/scenes/flow/measure.js +33 -0
  98. package/dist/scenes/flow/plan.d.ts +42 -0
  99. package/dist/scenes/flow/plan.js +117 -0
  100. package/dist/scenes/flow/scene.d.ts +2 -0
  101. package/dist/scenes/flow/scene.js +6 -0
  102. package/dist/scenes/flow/schema.d.ts +49 -0
  103. package/dist/scenes/flow/schema.js +60 -0
  104. package/dist/scenes/flow/style.css +54 -0
  105. package/dist/scenes/hero/index.d.ts +3 -0
  106. package/dist/scenes/hero/index.js +77 -0
  107. package/dist/scenes/hero/scene.d.ts +2 -0
  108. package/dist/scenes/hero/scene.js +6 -0
  109. package/dist/scenes/hero/style.css +143 -0
  110. package/dist/scenes/icon-grid/index.d.ts +1 -0
  111. package/dist/scenes/icon-grid/index.js +69 -0
  112. package/dist/scenes/icon-grid/scene.d.ts +2 -0
  113. package/dist/scenes/icon-grid/scene.js +6 -0
  114. package/dist/scenes/icon-grid/style.css +74 -0
  115. package/dist/scenes/kinetic/index.d.ts +1 -0
  116. package/dist/scenes/kinetic/index.js +62 -0
  117. package/dist/scenes/kinetic/scene.d.ts +2 -0
  118. package/dist/scenes/kinetic/scene.js +98 -0
  119. package/dist/scenes/kinetic/style.css +94 -0
  120. package/dist/scenes/kpi-grid/index.d.ts +1 -0
  121. package/dist/scenes/kpi-grid/index.js +161 -0
  122. package/dist/scenes/kpi-grid/scene.d.ts +2 -0
  123. package/dist/scenes/kpi-grid/scene.js +6 -0
  124. package/dist/scenes/kpi-grid/style.css +141 -0
  125. package/dist/scenes/lower-third/index.d.ts +1 -0
  126. package/dist/scenes/lower-third/index.js +33 -0
  127. package/dist/scenes/lower-third/scene.d.ts +2 -0
  128. package/dist/scenes/lower-third/scene.js +6 -0
  129. package/dist/scenes/lower-third/style.css +45 -0
  130. package/dist/scenes/media-frame/index.d.ts +1 -0
  131. package/dist/scenes/media-frame/index.js +99 -0
  132. package/dist/scenes/media-frame/scene.d.ts +2 -0
  133. package/dist/scenes/media-frame/scene.js +85 -0
  134. package/dist/scenes/media-frame/style.css +275 -0
  135. package/dist/scenes/outro/index.d.ts +1 -0
  136. package/dist/scenes/outro/index.js +54 -0
  137. package/dist/scenes/outro/scene.d.ts +2 -0
  138. package/dist/scenes/outro/scene.js +6 -0
  139. package/dist/scenes/outro/style.css +92 -0
  140. package/dist/scenes/poster/index.d.ts +3 -0
  141. package/dist/scenes/poster/index.js +62 -0
  142. package/dist/scenes/poster/scene.d.ts +2 -0
  143. package/dist/scenes/poster/scene.js +6 -0
  144. package/dist/scenes/poster/style.css +102 -0
  145. package/dist/scenes/progress/index.d.ts +1 -0
  146. package/dist/scenes/progress/index.js +40 -0
  147. package/dist/scenes/progress/scene.d.ts +2 -0
  148. package/dist/scenes/progress/scene.js +17 -0
  149. package/dist/scenes/progress/style.css +31 -0
  150. package/dist/scenes/quote/index.d.ts +3 -0
  151. package/dist/scenes/quote/index.js +69 -0
  152. package/dist/scenes/quote/scene.d.ts +2 -0
  153. package/dist/scenes/quote/scene.js +6 -0
  154. package/dist/scenes/quote/style.css +86 -0
  155. package/dist/scenes/scene3d/index.d.ts +1 -0
  156. package/dist/scenes/scene3d/index.js +59 -0
  157. package/dist/scenes/scene3d/scene.d.ts +2 -0
  158. package/dist/scenes/scene3d/scene.js +436 -0
  159. package/dist/scenes/scene3d/style.css +43 -0
  160. package/dist/scenes/stat/index.d.ts +4 -0
  161. package/dist/scenes/stat/index.js +72 -0
  162. package/dist/scenes/stat/scene.d.ts +2 -0
  163. package/dist/scenes/stat/scene.js +6 -0
  164. package/dist/scenes/stat/style.css +95 -0
  165. package/dist/scenes/steps/index.d.ts +1 -0
  166. package/dist/scenes/steps/index.js +147 -0
  167. package/dist/scenes/steps/scene.d.ts +2 -0
  168. package/dist/scenes/steps/scene.js +6 -0
  169. package/dist/scenes/steps/style.css +190 -0
  170. package/dist/scenes/sticker/index.d.ts +1 -0
  171. package/dist/scenes/sticker/index.js +48 -0
  172. package/dist/scenes/sticker/scene.d.ts +2 -0
  173. package/dist/scenes/sticker/scene.js +57 -0
  174. package/dist/scenes/sticker/style.css +88 -0
  175. package/dist/scenes/table/index.d.ts +1 -0
  176. package/dist/scenes/table/index.js +131 -0
  177. package/dist/scenes/table/scene.d.ts +2 -0
  178. package/dist/scenes/table/scene.js +6 -0
  179. package/dist/scenes/table/style.css +102 -0
  180. package/dist/scenes/timeline/index.d.ts +1 -0
  181. package/dist/scenes/timeline/index.js +68 -0
  182. package/dist/scenes/timeline/scene.d.ts +2 -0
  183. package/dist/scenes/timeline/scene.js +6 -0
  184. package/dist/scenes/timeline/style.css +161 -0
  185. package/dist/scenes/title/index.d.ts +1 -0
  186. package/dist/scenes/title/index.js +52 -0
  187. package/dist/scenes/title/scene.d.ts +2 -0
  188. package/dist/scenes/title/scene.js +6 -0
  189. package/dist/scenes/title/style.css +80 -0
  190. package/dist/scenes/waveform/index.d.ts +1 -0
  191. package/dist/scenes/waveform/index.js +39 -0
  192. package/dist/scenes/waveform/scene.d.ts +2 -0
  193. package/dist/scenes/waveform/scene.js +35 -0
  194. package/dist/scenes/waveform/style.css +44 -0
  195. package/dist/shared/fit-text.d.ts +24 -0
  196. package/dist/shared/fit-text.js +82 -0
  197. package/dist/shared/format.d.ts +24 -0
  198. package/dist/shared/format.js +59 -0
  199. package/dist/shared/frame.d.ts +14 -0
  200. package/dist/shared/frame.js +21 -0
  201. package/dist/shared/icon-search.d.ts +9 -0
  202. package/dist/shared/icon-search.js +26 -0
  203. package/dist/shared/icon.d.ts +15 -0
  204. package/dist/shared/icon.js +28 -0
  205. package/dist/shared/index.d.ts +11 -0
  206. package/dist/shared/index.js +7 -0
  207. package/dist/shared/markup.d.ts +12 -0
  208. package/dist/shared/markup.js +43 -0
  209. package/dist/shared/measure.d.ts +7 -0
  210. package/dist/shared/measure.js +34 -0
  211. package/dist/shared/motion.d.ts +94 -0
  212. package/dist/shared/motion.js +117 -0
  213. package/dist/source-hash.d.ts +4 -0
  214. package/dist/source-hash.js +26 -0
  215. package/dist/test-fixtures.d.ts +9 -0
  216. package/dist/test-fixtures.js +22 -0
  217. package/dist/text-file.d.ts +1 -0
  218. package/dist/text-file.js +3 -0
  219. package/dist/words.d.ts +11 -0
  220. package/dist/words.js +93 -0
  221. package/package.json +36 -3
@@ -0,0 +1,68 @@
1
+ import { bugScene } from "./scenes/bug/index.js";
2
+ import { bulletsScene } from "./scenes/bullets/index.js";
3
+ import { calloutScene } from "./scenes/callout/index.js";
4
+ import { captionScene } from "./scenes/caption/index.js";
5
+ import { chapterScene } from "./scenes/chapter/index.js";
6
+ import { chartScene } from "./scenes/chart/index.js";
7
+ import { codeScene } from "./scenes/code/index.js";
8
+ import { compareScene } from "./scenes/compare/index.js";
9
+ import { countdownScene } from "./scenes/countdown/index.js";
10
+ import { flowScene } from "./scenes/flow/index.js";
11
+ import { heroScene } from "./scenes/hero/index.js";
12
+ import { iconGridScene } from "./scenes/icon-grid/index.js";
13
+ import { kineticScene } from "./scenes/kinetic/index.js";
14
+ import { kpiGridScene } from "./scenes/kpi-grid/index.js";
15
+ import { lowerThirdScene } from "./scenes/lower-third/index.js";
16
+ import { mediaFrameScene } from "./scenes/media-frame/index.js";
17
+ import { outroScene } from "./scenes/outro/index.js";
18
+ import { posterScene } from "./scenes/poster/index.js";
19
+ import { progressScene } from "./scenes/progress/index.js";
20
+ import { quoteScene } from "./scenes/quote/index.js";
21
+ import { scene3dScene } from "./scenes/scene3d/index.js";
22
+ import { statScene } from "./scenes/stat/index.js";
23
+ import { stepsScene } from "./scenes/steps/index.js";
24
+ import { stickerScene } from "./scenes/sticker/index.js";
25
+ import { tableScene } from "./scenes/table/index.js";
26
+ import { timelineScene } from "./scenes/timeline/index.js";
27
+ import { titleScene } from "./scenes/title/index.js";
28
+ import { waveformScene } from "./scenes/waveform/index.js";
29
+ /** The single list of scenes: adding a scene is one import and one entry here. */
30
+ const plugins = [
31
+ titleScene,
32
+ heroScene,
33
+ posterScene,
34
+ bulletsScene,
35
+ statScene,
36
+ flowScene,
37
+ outroScene,
38
+ lowerThirdScene,
39
+ bugScene,
40
+ progressScene,
41
+ countdownScene,
42
+ chapterScene,
43
+ captionScene,
44
+ compareScene,
45
+ calloutScene,
46
+ quoteScene,
47
+ iconGridScene,
48
+ stepsScene,
49
+ timelineScene,
50
+ mediaFrameScene,
51
+ kineticScene,
52
+ codeScene,
53
+ kpiGridScene,
54
+ chartScene,
55
+ tableScene,
56
+ scene3dScene,
57
+ stickerScene,
58
+ waveformScene,
59
+ ];
60
+ export const sceneRegistry = new Map(plugins.map((plugin) => [plugin.type, plugin]));
61
+ export const sceneTypes = [...sceneRegistry.keys()];
62
+ export const getScene = (type) => {
63
+ const plugin = sceneRegistry.get(type);
64
+ if (!plugin) {
65
+ throw new Error(`unknown scene type "${type}"; use one of: ${sceneTypes.join("|")}`);
66
+ }
67
+ return plugin;
68
+ };
@@ -0,0 +1,21 @@
1
+ export { clamp01, ease } from "./easing.js";
2
+ /**
3
+ * Normalized interval in seconds. A zero duration is an instantaneous cut;
4
+ * negative durations are invalid. No prior samples or DOM state are read.
5
+ * @param {number} time
6
+ * @param {number} start
7
+ * @param {number} duration
8
+ */
9
+ export declare const progress: (time: number, start: number, duration: number) => number;
10
+ /** Numeric interpolation; amount may overshoot for spring or anticipation.
11
+ * @param {number} from
12
+ * @param {number} to
13
+ * @param {number} amount
14
+ */
15
+ export declare const interpolate: (from: number, to: number, amount: number) => number;
16
+ /** Start time for an item. A negative step expresses a reversed order.
17
+ * @param {number} index non-negative integer
18
+ * @param {number} step seconds between items
19
+ * @param {number} [start=0] first item's time
20
+ */
21
+ export declare const stagger: (index: number, step: number, start?: number) => number;
@@ -0,0 +1,43 @@
1
+ // @ts-check
2
+ // Style-neutral authoring helpers. Every sample depends only on its inputs.
3
+ export { clamp01, ease } from "./easing.js";
4
+ /** @param {number[]} values */
5
+ const finite = (...values) => {
6
+ if (!values.every(Number.isFinite))
7
+ throw new RangeError("composition inputs must be finite");
8
+ };
9
+ /**
10
+ * Normalized interval in seconds. A zero duration is an instantaneous cut;
11
+ * negative durations are invalid. No prior samples or DOM state are read.
12
+ * @param {number} time
13
+ * @param {number} start
14
+ * @param {number} duration
15
+ */
16
+ export const progress = (time, start, duration) => {
17
+ finite(time, start, duration);
18
+ if (duration < 0)
19
+ throw new RangeError("duration must be non-negative");
20
+ if (duration === 0)
21
+ return time < start ? 0 : 1;
22
+ return Math.max(0, Math.min(1, (time - start) / duration));
23
+ };
24
+ /** Numeric interpolation; amount may overshoot for spring or anticipation.
25
+ * @param {number} from
26
+ * @param {number} to
27
+ * @param {number} amount
28
+ */
29
+ export const interpolate = (from, to, amount) => {
30
+ finite(from, to, amount);
31
+ return from + (to - from) * amount;
32
+ };
33
+ /** Start time for an item. A negative step expresses a reversed order.
34
+ * @param {number} index non-negative integer
35
+ * @param {number} step seconds between items
36
+ * @param {number} [start=0] first item's time
37
+ */
38
+ export const stagger = (index, step, start = 0) => {
39
+ if (!Number.isInteger(index) || index < 0)
40
+ throw new RangeError("index must be a non-negative integer");
41
+ finite(step, start);
42
+ return start + index * step;
43
+ };
@@ -0,0 +1,14 @@
1
+ export type EaseFn = (t: number) => number;
2
+ /** @typedef {(t: number) => number} EaseFn */
3
+ /** Clamp to [0, 1]; non-finite input maps to 0.
4
+ * @param {number} t
5
+ * @returns {number}
6
+ */
7
+ export declare const clamp01: (t: number) => number;
8
+ export declare const EASE_NAMES: string[];
9
+ /**
10
+ * @param {string} name one of EASE_NAMES
11
+ * @param {number} t progress, clamped to [0, 1]
12
+ * @returns {number}
13
+ */
14
+ export declare const ease: (name: string, t: number) => number;
@@ -0,0 +1,45 @@
1
+ // @ts-check
2
+ // Pure easing functions: no DOM, no clock, no state.
3
+ /** @typedef {(t: number) => number} EaseFn */
4
+ /** Clamp to [0, 1]; non-finite input maps to 0.
5
+ * @param {number} t
6
+ * @returns {number}
7
+ */
8
+ export const clamp01 = (t) => (t > 0 ? Math.min(t, 1) : 0);
9
+ /** Normalised settle time of the closed-form spring response. */
10
+ const SPRING_SPAN = 1;
11
+ /**
12
+ * Closed-form underdamped spring step response, rescaled so f(1) = 1 exactly.
13
+ * @param {number} damping damping ratio, 0 < damping < 1
14
+ * @param {number} omega natural frequency over the normalised span
15
+ * @returns {EaseFn}
16
+ */
17
+ const spring = (damping, omega) => {
18
+ const decay = damping * omega;
19
+ const damped = omega * Math.sqrt(1 - damping * damping);
20
+ /** @param {number} x */
21
+ const step = (x) => 1 - Math.exp(-decay * x) * (Math.cos(damped * x) + (decay / damped) * Math.sin(damped * x));
22
+ const settled = step(SPRING_SPAN);
23
+ return (t) => step(t * SPRING_SPAN) / settled;
24
+ };
25
+ /** @type {Readonly<Record<string, EaseFn>>} */
26
+ const EASINGS = {
27
+ linear: (t) => t,
28
+ in: (t) => t * t * t,
29
+ out: (t) => 1 - (1 - t) ** 3,
30
+ "in-out": (t) => (t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2),
31
+ "spring-soft": spring(0.85, 9),
32
+ "spring-snappy": spring(0.6, 14),
33
+ };
34
+ export const EASE_NAMES = Object.keys(EASINGS);
35
+ /**
36
+ * @param {string} name one of EASE_NAMES
37
+ * @param {number} t progress, clamped to [0, 1]
38
+ * @returns {number}
39
+ */
40
+ export const ease = (name, t) => {
41
+ const fn = EASINGS[name];
42
+ if (!fn)
43
+ throw new Error(`unknown ease "${name}"; use one of: ${EASE_NAMES.join("|")}`);
44
+ return fn(clamp01(t));
45
+ };
@@ -0,0 +1,50 @@
1
+ export type SceneConfig = {
2
+ ease?: string;
3
+ move?: string;
4
+ focus?: string;
5
+ };
6
+ export type RuntimeContext = {
7
+ fps?: number;
8
+ duration?: number;
9
+ };
10
+ export type Plan = import("./timeline.js").Plan;
11
+ export type Runtime = {
12
+ seek(t: number): void;
13
+ dispose(): void;
14
+ ready: Promise<void>;
15
+ };
16
+ export type CreateScene = (root: HTMLElement, config: SceneConfig, ctx: RuntimeContext) => Runtime;
17
+ export type Counter = {
18
+ el: HTMLElement;
19
+ text: string;
20
+ track: import("./timeline.js").Track;
21
+ };
22
+ export type Item = {
23
+ el: HTMLElement | SVGElement;
24
+ kind: string;
25
+ keys: string;
26
+ track: import("./timeline.js").Track;
27
+ };
28
+ /**
29
+ * Mounts every [data-anim] element of a scene fragment on one entrance timeline.
30
+ * @param {HTMLElement} root
31
+ * @param {SceneConfig} config
32
+ * @param {RuntimeContext} ctx
33
+ * @param {Plan} plan
34
+ * @returns {Runtime}
35
+ */
36
+ export declare const mountTimeline: (root: HTMLElement, config: SceneConfig, ctx: RuntimeContext, plan: Plan) => Runtime;
37
+ /**
38
+ * An overlay: the entrance timeline plus an eased fade of the whole layer in and out of its
39
+ * time span. `hooks.update(t)` draws scene-specific state; `hooks.reset()` undoes it on dispose.
40
+ * @param {HTMLElement} root
41
+ * @param {SceneConfig} config
42
+ * @param {RuntimeContext} ctx
43
+ * @param {Plan} plan
44
+ * @param {{ update?: (t: number) => void, reset?: () => void }} [hooks]
45
+ * @returns {Runtime}
46
+ */
47
+ export declare const mountOverlay: (root: HTMLElement, config: SceneConfig, ctx: RuntimeContext, plan: Plan, hooks?: {
48
+ update?: (t: number) => void;
49
+ reset?: () => void;
50
+ }) => Runtime;
@@ -0,0 +1,153 @@
1
+ // @ts-check
2
+ // Thin DOM layer: seek(t) writes styles as a pure function of t.
3
+ import { cameraScale, countScale, countText, countTrack, dimOpacity, driftOf, entranceState, entranceTrack, overlayEnvelope, sample, } from "./timeline.js";
4
+ /** @typedef {{ ease?: string, move?: string, focus?: string }} SceneConfig */
5
+ /** @typedef {{ fps?: number, duration?: number }} RuntimeContext */
6
+ /** @typedef {import("./timeline.js").Plan} Plan */
7
+ /** @typedef {{ seek(t: number): void, dispose(): void, ready: Promise<void> }} Runtime */
8
+ /** @typedef {(root: HTMLElement, config: SceneConfig, ctx: RuntimeContext) => Runtime} CreateScene */
9
+ /** @typedef {{ el: HTMLElement, text: string, track: import("./timeline.js").Track }} Counter */
10
+ /** @typedef {{ el: HTMLElement | SVGElement, kind: string, keys: string, track: import("./timeline.js").Track }} Item */
11
+ const DEFAULT_FPS = 30;
12
+ /**
13
+ * Config and ctx win over the data-* attributes the emitter put on the root.
14
+ * @param {HTMLElement} root
15
+ * @param {SceneConfig} config
16
+ * @param {RuntimeContext} ctx
17
+ */
18
+ const readSettings = (root, config, ctx) => ({
19
+ ease: config.ease ?? root.dataset.ease ?? "out",
20
+ move: config.move ?? root.dataset.move ?? "static",
21
+ focus: config.focus ?? root.dataset.focus ?? "",
22
+ fps: ctx.fps ?? Number(root.dataset.fps ?? DEFAULT_FPS),
23
+ duration: ctx.duration ?? Number(root.dataset.duration ?? 0),
24
+ });
25
+ /**
26
+ * @param {HTMLElement} root
27
+ * @param {ReturnType<typeof readSettings>} settings
28
+ * @param {Plan} plan
29
+ * @returns {Item[]}
30
+ */
31
+ const collectItems = (root, settings, plan) => Array.from(root.querySelectorAll("[data-anim]"), (node) => {
32
+ const el = /** @type {HTMLElement | SVGElement} */ (node);
33
+ const step = Number(el.dataset.step ?? 0);
34
+ return {
35
+ el,
36
+ kind: el.dataset.anim ?? "rise",
37
+ keys: el.dataset.focusKey ?? "",
38
+ track: entranceTrack(step, plan, settings.fps, settings.ease),
39
+ };
40
+ });
41
+ /**
42
+ * @param {Item} item
43
+ * @param {number} t
44
+ * @param {string} focus
45
+ * @param {number} rise
46
+ */
47
+ const applyItem = (item, t, focus, rise) => {
48
+ const state = entranceState(item.kind, sample(item.track, t), rise);
49
+ item.el.style.opacity = String(state.opacity * dimOpacity(focus, item.keys));
50
+ if (item.kind === "rise")
51
+ item.el.style.transform = `translateY(${state.y}px)`;
52
+ if (item.kind === "draw")
53
+ item.el.style.strokeDashoffset = String(state.dash);
54
+ if (item.kind === "grow")
55
+ item.el.style.transform = `scaleX(${state.scale})`;
56
+ };
57
+ /** @param {Item} item */
58
+ const clearItem = (item) => {
59
+ for (const name of ["opacity", "transform", "stroke-dashoffset"]) {
60
+ item.el.style.removeProperty(name);
61
+ }
62
+ };
63
+ /**
64
+ * Elements whose text counts up to the number they carry in data-count.
65
+ * @param {HTMLElement} root
66
+ * @param {ReturnType<typeof readSettings>} settings
67
+ * @param {Plan} plan
68
+ * @returns {Counter[]}
69
+ */
70
+ const collectCounters = (root, settings, plan) => Array.from(root.querySelectorAll("[data-count]"), (node) => {
71
+ const el = /** @type {HTMLElement} */ (node);
72
+ const step = Number(el.dataset.step ?? 0);
73
+ return {
74
+ el,
75
+ text: el.dataset.count ?? "",
76
+ track: countTrack(step, plan, settings.fps, settings.ease, settings.duration),
77
+ };
78
+ });
79
+ /**
80
+ * @param {Counter} counter
81
+ * @param {number} t
82
+ */
83
+ const applyCounter = (counter, t) => {
84
+ const progress = sample(counter.track, t);
85
+ counter.el.textContent = countText(counter.text, progress);
86
+ counter.el.style.transform = `scale(${countScale(progress)})`;
87
+ };
88
+ /**
89
+ * @param {HTMLElement} root
90
+ * @returns {Promise<void>}
91
+ */
92
+ const fontsReady = (root) => root.ownerDocument.fonts?.ready.then(() => undefined) ?? Promise.resolve();
93
+ /**
94
+ * Mounts every [data-anim] element of a scene fragment on one entrance timeline.
95
+ * @param {HTMLElement} root
96
+ * @param {SceneConfig} config
97
+ * @param {RuntimeContext} ctx
98
+ * @param {Plan} plan
99
+ * @returns {Runtime}
100
+ */
101
+ export const mountTimeline = (root, config, ctx, plan) => {
102
+ const settings = readSettings(root, config, ctx);
103
+ const items = collectItems(root, settings, plan);
104
+ const counters = collectCounters(root, settings, plan);
105
+ /** @param {number} t */
106
+ const seek = (t) => {
107
+ for (const item of items)
108
+ applyItem(item, t, settings.focus, plan.rise);
109
+ for (const counter of counters)
110
+ applyCounter(counter, t);
111
+ root.style.setProperty("--drift", String(driftOf(t)));
112
+ root.style.transform = `scale(${cameraScale(settings.move, t, settings.duration)})`;
113
+ };
114
+ const dispose = () => {
115
+ for (const item of items)
116
+ clearItem(item);
117
+ for (const counter of counters) {
118
+ counter.el.textContent = counter.text;
119
+ counter.el.style.removeProperty("transform");
120
+ }
121
+ root.style.removeProperty("transform");
122
+ root.style.removeProperty("--drift");
123
+ };
124
+ seek(0);
125
+ return { seek, dispose, ready: fontsReady(root) };
126
+ };
127
+ /**
128
+ * An overlay: the entrance timeline plus an eased fade of the whole layer in and out of its
129
+ * time span. `hooks.update(t)` draws scene-specific state; `hooks.reset()` undoes it on dispose.
130
+ * @param {HTMLElement} root
131
+ * @param {SceneConfig} config
132
+ * @param {RuntimeContext} ctx
133
+ * @param {Plan} plan
134
+ * @param {{ update?: (t: number) => void, reset?: () => void }} [hooks]
135
+ * @returns {Runtime}
136
+ */
137
+ export const mountOverlay = (root, config, ctx, plan, hooks = {}) => {
138
+ const settings = readSettings(root, config, ctx);
139
+ const inner = mountTimeline(root, config, ctx, plan);
140
+ /** @param {number} t */
141
+ const seek = (t) => {
142
+ inner.seek(t);
143
+ root.style.opacity = String(overlayEnvelope(t, settings.duration, settings.ease));
144
+ hooks.update?.(t);
145
+ };
146
+ const dispose = () => {
147
+ inner.dispose();
148
+ root.style.removeProperty("opacity");
149
+ hooks.reset?.();
150
+ };
151
+ seek(0);
152
+ return { seek, dispose, ready: inner.ready };
153
+ };
@@ -0,0 +1,147 @@
1
+ export type Track = {
2
+ from: number;
3
+ to: number;
4
+ start: number;
5
+ end: number;
6
+ ease: string;
7
+ };
8
+ export type Plan = {
9
+ baseDelay: number;
10
+ staggerFrames: number;
11
+ rise: number;
12
+ };
13
+ export type EntranceState = {
14
+ opacity: number;
15
+ y: number;
16
+ dash: number;
17
+ scale: number;
18
+ };
19
+ /** @typedef {{ from: number, to: number, start: number, end: number, ease: string }} Track */
20
+ /** @typedef {{ baseDelay: number, staggerFrames: number, rise: number }} Plan */
21
+ /** @typedef {{ opacity: number, y: number, dash: number, scale: number }} EntranceState */
22
+ export declare const ENTRANCE_SECONDS = 0.6;
23
+ export declare const MIN_STAGGER_FRAMES = 3;
24
+ export declare const MAX_STAGGER_FRAMES = 6;
25
+ export declare const COUNT_SECONDS = 1.4;
26
+ /** Share of the scene a count-up may take, and its ceiling in seconds. */
27
+ export declare const COUNT_SHARE = 0.25;
28
+ export declare const MAX_COUNT_SECONDS = 3;
29
+ /** Opacity of a "rise" element at progress 0, so no scene opens on a blank frame. */
30
+ export declare const RISE_FLOOR = 0.45;
31
+ /** Size of a counting number at progress 0, relative to its final size. */
32
+ export declare const COUNT_SCALE_FROM = 0.7;
33
+ /** Fade-in and fade-out length of an overlay window, in seconds. */
34
+ export declare const ENVELOPE_SECONDS = 0.4;
35
+ /** Extra size of a countdown number at the start of each step. */
36
+ export declare const PULSE_SCALE = 0.25;
37
+ /**
38
+ * Value of a single-segment track at time t (seconds).
39
+ * @param {Track} track
40
+ * @param {number} t
41
+ * @returns {number}
42
+ */
43
+ export declare const sample: (track: Track, t: number) => number;
44
+ /**
45
+ * Stagger between consecutive steps, in seconds, from a frame count.
46
+ * @param {number} frames 3-6
47
+ * @param {number} fps
48
+ * @returns {number}
49
+ */
50
+ export declare const staggerSeconds: (frames: number, fps: number) => number;
51
+ /**
52
+ * Progress track (0 -> 1) of the entrance of step `step`.
53
+ * @param {number} step
54
+ * @param {Plan} plan
55
+ * @param {number} fps
56
+ * @param {string} easing
57
+ * @returns {Track}
58
+ */
59
+ export declare const entranceTrack: (step: number, plan: Plan, fps: number, easing: string) => Track;
60
+ /**
61
+ * Visual state of an element kind at entrance progress p.
62
+ * @param {string} kind rise | fade | draw | grow
63
+ * @param {number} p progress; may overshoot for springs
64
+ * @param {number} rise rise distance in px
65
+ * @returns {EntranceState}
66
+ */
67
+ export declare const entranceState: (kind: string, p: number, rise: number) => EntranceState;
68
+ /**
69
+ * Count-up length: COUNT_SECONDS, stretched to a quarter of a long scene (max MAX_COUNT_SECONDS).
70
+ * @param {number} duration scene seconds; 0 or unknown keeps COUNT_SECONDS
71
+ * @returns {number}
72
+ */
73
+ export declare const countSeconds: (duration: number) => number;
74
+ /**
75
+ * Track of a count-up that starts with the entrance of `step`.
76
+ * @param {number} step
77
+ * @param {Plan} plan
78
+ * @param {number} fps
79
+ * @param {string} easing
80
+ * @param {number} [duration] scene seconds
81
+ * @returns {Track}
82
+ */
83
+ export declare const countTrack: (step: number, plan: Plan, fps: number, easing: string, duration?: number) => Track;
84
+ /**
85
+ * Scale of a counting number at count progress p: grows from COUNT_SCALE_FROM to 1.
86
+ * @param {number} p
87
+ * @returns {number}
88
+ */
89
+ export declare const countScale: (p: number) => number;
90
+ /**
91
+ * The first number of `text` scaled to progress p, keeping prefix, suffix, decimals and
92
+ * thousands separators; p >= 1 and text without one leading number return `text` verbatim.
93
+ * @param {string} text
94
+ * @param {number} p
95
+ * @returns {string}
96
+ */
97
+ export declare const countText: (text: string, p: number) => string;
98
+ /**
99
+ * Slow background drift in [-1, 1]: one cycle every ~18 s.
100
+ * @param {number} t seconds
101
+ * @returns {number}
102
+ */
103
+ export declare const driftOf: (t: number) => number;
104
+ /**
105
+ * Root scale of the supported camera moves; other moves are applied by the build.
106
+ * @param {string} move
107
+ * @param {number} t
108
+ * @param {number} duration
109
+ * @returns {number}
110
+ */
111
+ export declare const cameraScale: (move: string, t: number, duration: number) => number;
112
+ /**
113
+ * Opacity multiplier of `focus`: elements outside the focus dim.
114
+ * @param {string} focus focused key; empty means no focus
115
+ * @param {string} keys space-separated keys of the element; empty means never dim
116
+ * @returns {number}
117
+ */
118
+ export declare const dimOpacity: (focus: string, keys: string) => number;
119
+ /**
120
+ * Opacity of an overlay at local time t: eased fade in, hold, eased fade out.
121
+ * @param {number} t seconds from the start of the overlay window
122
+ * @param {number} duration window length in seconds
123
+ * @param {string} easing
124
+ * @returns {number}
125
+ */
126
+ export declare const overlayEnvelope: (t: number, duration: number, easing: string) => number;
127
+ /**
128
+ * Countdown value and pulse scale at local time t: one equal step per integer from -> to.
129
+ * @param {number} t
130
+ * @param {number} duration
131
+ * @param {number} from
132
+ * @param {number} to
133
+ * @returns {{ value: number, scale: number }}
134
+ */
135
+ export declare const countdownState: (t: number, duration: number, from: number, to: number) => {
136
+ value: number;
137
+ scale: number;
138
+ };
139
+ /**
140
+ * Linear value from -> to across the duration (progress bars).
141
+ * @param {number} t
142
+ * @param {number} duration
143
+ * @param {number} from
144
+ * @param {number} to
145
+ * @returns {number}
146
+ */
147
+ export declare const spanProgress: (t: number, duration: number, from: number, to: number) => number;