@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.
- package/LICENSE +21 -0
- package/README.md +39 -2
- package/dist/catalog.d.ts +17 -0
- package/dist/catalog.js +51 -0
- package/dist/custom.d.ts +86 -0
- package/dist/custom.js +176 -0
- package/dist/define-scene.d.ts +31 -0
- package/dist/define-scene.js +128 -0
- package/dist/html.d.ts +5 -0
- package/dist/html.js +14 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/position.d.ts +21 -0
- package/dist/position.js +16 -0
- package/dist/registry.d.ts +4 -0
- package/dist/registry.js +68 -0
- package/dist/runtime/compose.d.ts +21 -0
- package/dist/runtime/compose.js +43 -0
- package/dist/runtime/easing.d.ts +14 -0
- package/dist/runtime/easing.js +45 -0
- package/dist/runtime/mount.d.ts +50 -0
- package/dist/runtime/mount.js +153 -0
- package/dist/runtime/timeline.d.ts +147 -0
- package/dist/runtime/timeline.js +199 -0
- package/dist/runtime-files.d.ts +4 -0
- package/dist/runtime-files.js +18 -0
- package/dist/scene-contract.d.ts +7 -0
- package/dist/scene-contract.js +104 -0
- package/dist/scene3d/post.d.ts +12 -0
- package/dist/scene3d/post.js +16 -0
- package/dist/scene3d/reflector.d.ts +9 -0
- package/dist/scene3d/reflector.js +17 -0
- package/dist/scene3d/textures.d.ts +9 -0
- package/dist/scene3d/textures.js +11 -0
- package/dist/scene3d/volumetric.d.ts +9 -0
- package/dist/scene3d/volumetric.js +16 -0
- package/dist/scenes/bug/index.d.ts +1 -0
- package/dist/scenes/bug/index.js +45 -0
- package/dist/scenes/bug/scene.d.ts +2 -0
- package/dist/scenes/bug/scene.js +6 -0
- package/dist/scenes/bug/style.css +30 -0
- package/dist/scenes/bullets/index.d.ts +1 -0
- package/dist/scenes/bullets/index.js +128 -0
- package/dist/scenes/bullets/scene.d.ts +2 -0
- package/dist/scenes/bullets/scene.js +6 -0
- package/dist/scenes/bullets/style.css +91 -0
- package/dist/scenes/callout/index.d.ts +9 -0
- package/dist/scenes/callout/index.js +95 -0
- package/dist/scenes/callout/scene.d.ts +2 -0
- package/dist/scenes/callout/scene.js +43 -0
- package/dist/scenes/callout/style.css +97 -0
- package/dist/scenes/caption/index.d.ts +1 -0
- package/dist/scenes/caption/index.js +72 -0
- package/dist/scenes/caption/scene.d.ts +11 -0
- package/dist/scenes/caption/scene.js +33 -0
- package/dist/scenes/caption/style.css +33 -0
- package/dist/scenes/chapter/index.d.ts +1 -0
- package/dist/scenes/chapter/index.js +29 -0
- package/dist/scenes/chapter/scene.d.ts +2 -0
- package/dist/scenes/chapter/scene.js +6 -0
- package/dist/scenes/chapter/style.css +25 -0
- package/dist/scenes/chart/index.d.ts +1 -0
- package/dist/scenes/chart/index.js +24 -0
- package/dist/scenes/chart/render.d.ts +4 -0
- package/dist/scenes/chart/render.js +257 -0
- package/dist/scenes/chart/scale.d.ts +11 -0
- package/dist/scenes/chart/scale.js +53 -0
- package/dist/scenes/chart/scene.d.ts +2 -0
- package/dist/scenes/chart/scene.js +73 -0
- package/dist/scenes/chart/schema.d.ts +36 -0
- package/dist/scenes/chart/schema.js +109 -0
- package/dist/scenes/chart/style.css +425 -0
- package/dist/scenes/code/index.d.ts +1 -0
- package/dist/scenes/code/index.js +109 -0
- package/dist/scenes/code/scene.d.ts +2 -0
- package/dist/scenes/code/scene.js +108 -0
- package/dist/scenes/code/style.css +155 -0
- package/dist/scenes/code/tokenize.d.ts +8 -0
- package/dist/scenes/code/tokenize.js +145 -0
- package/dist/scenes/compare/index.d.ts +1 -0
- package/dist/scenes/compare/index.js +128 -0
- package/dist/scenes/compare/scene.d.ts +2 -0
- package/dist/scenes/compare/scene.js +58 -0
- package/dist/scenes/compare/style.css +187 -0
- package/dist/scenes/countdown/index.d.ts +1 -0
- package/dist/scenes/countdown/index.js +46 -0
- package/dist/scenes/countdown/scene.d.ts +2 -0
- package/dist/scenes/countdown/scene.js +21 -0
- package/dist/scenes/countdown/style.css +32 -0
- package/dist/scenes/flow/index.d.ts +1 -0
- package/dist/scenes/flow/index.js +66 -0
- package/dist/scenes/flow/inspect.d.ts +26 -0
- package/dist/scenes/flow/inspect.js +25 -0
- package/dist/scenes/flow/layout.d.ts +83 -0
- package/dist/scenes/flow/layout.js +222 -0
- package/dist/scenes/flow/measure.d.ts +15 -0
- package/dist/scenes/flow/measure.js +33 -0
- package/dist/scenes/flow/plan.d.ts +42 -0
- package/dist/scenes/flow/plan.js +117 -0
- package/dist/scenes/flow/scene.d.ts +2 -0
- package/dist/scenes/flow/scene.js +6 -0
- package/dist/scenes/flow/schema.d.ts +49 -0
- package/dist/scenes/flow/schema.js +60 -0
- package/dist/scenes/flow/style.css +54 -0
- package/dist/scenes/hero/index.d.ts +3 -0
- package/dist/scenes/hero/index.js +77 -0
- package/dist/scenes/hero/scene.d.ts +2 -0
- package/dist/scenes/hero/scene.js +6 -0
- package/dist/scenes/hero/style.css +143 -0
- package/dist/scenes/icon-grid/index.d.ts +1 -0
- package/dist/scenes/icon-grid/index.js +69 -0
- package/dist/scenes/icon-grid/scene.d.ts +2 -0
- package/dist/scenes/icon-grid/scene.js +6 -0
- package/dist/scenes/icon-grid/style.css +74 -0
- package/dist/scenes/kinetic/index.d.ts +1 -0
- package/dist/scenes/kinetic/index.js +62 -0
- package/dist/scenes/kinetic/scene.d.ts +2 -0
- package/dist/scenes/kinetic/scene.js +98 -0
- package/dist/scenes/kinetic/style.css +94 -0
- package/dist/scenes/kpi-grid/index.d.ts +1 -0
- package/dist/scenes/kpi-grid/index.js +161 -0
- package/dist/scenes/kpi-grid/scene.d.ts +2 -0
- package/dist/scenes/kpi-grid/scene.js +6 -0
- package/dist/scenes/kpi-grid/style.css +141 -0
- package/dist/scenes/lower-third/index.d.ts +1 -0
- package/dist/scenes/lower-third/index.js +33 -0
- package/dist/scenes/lower-third/scene.d.ts +2 -0
- package/dist/scenes/lower-third/scene.js +6 -0
- package/dist/scenes/lower-third/style.css +45 -0
- package/dist/scenes/media-frame/index.d.ts +1 -0
- package/dist/scenes/media-frame/index.js +99 -0
- package/dist/scenes/media-frame/scene.d.ts +2 -0
- package/dist/scenes/media-frame/scene.js +85 -0
- package/dist/scenes/media-frame/style.css +275 -0
- package/dist/scenes/outro/index.d.ts +1 -0
- package/dist/scenes/outro/index.js +54 -0
- package/dist/scenes/outro/scene.d.ts +2 -0
- package/dist/scenes/outro/scene.js +6 -0
- package/dist/scenes/outro/style.css +92 -0
- package/dist/scenes/poster/index.d.ts +3 -0
- package/dist/scenes/poster/index.js +62 -0
- package/dist/scenes/poster/scene.d.ts +2 -0
- package/dist/scenes/poster/scene.js +6 -0
- package/dist/scenes/poster/style.css +102 -0
- package/dist/scenes/progress/index.d.ts +1 -0
- package/dist/scenes/progress/index.js +40 -0
- package/dist/scenes/progress/scene.d.ts +2 -0
- package/dist/scenes/progress/scene.js +17 -0
- package/dist/scenes/progress/style.css +31 -0
- package/dist/scenes/quote/index.d.ts +3 -0
- package/dist/scenes/quote/index.js +69 -0
- package/dist/scenes/quote/scene.d.ts +2 -0
- package/dist/scenes/quote/scene.js +6 -0
- package/dist/scenes/quote/style.css +86 -0
- package/dist/scenes/scene3d/index.d.ts +1 -0
- package/dist/scenes/scene3d/index.js +59 -0
- package/dist/scenes/scene3d/scene.d.ts +2 -0
- package/dist/scenes/scene3d/scene.js +436 -0
- package/dist/scenes/scene3d/style.css +43 -0
- package/dist/scenes/stat/index.d.ts +4 -0
- package/dist/scenes/stat/index.js +72 -0
- package/dist/scenes/stat/scene.d.ts +2 -0
- package/dist/scenes/stat/scene.js +6 -0
- package/dist/scenes/stat/style.css +95 -0
- package/dist/scenes/steps/index.d.ts +1 -0
- package/dist/scenes/steps/index.js +147 -0
- package/dist/scenes/steps/scene.d.ts +2 -0
- package/dist/scenes/steps/scene.js +6 -0
- package/dist/scenes/steps/style.css +190 -0
- package/dist/scenes/sticker/index.d.ts +1 -0
- package/dist/scenes/sticker/index.js +48 -0
- package/dist/scenes/sticker/scene.d.ts +2 -0
- package/dist/scenes/sticker/scene.js +57 -0
- package/dist/scenes/sticker/style.css +88 -0
- package/dist/scenes/table/index.d.ts +1 -0
- package/dist/scenes/table/index.js +131 -0
- package/dist/scenes/table/scene.d.ts +2 -0
- package/dist/scenes/table/scene.js +6 -0
- package/dist/scenes/table/style.css +102 -0
- package/dist/scenes/timeline/index.d.ts +1 -0
- package/dist/scenes/timeline/index.js +68 -0
- package/dist/scenes/timeline/scene.d.ts +2 -0
- package/dist/scenes/timeline/scene.js +6 -0
- package/dist/scenes/timeline/style.css +161 -0
- package/dist/scenes/title/index.d.ts +1 -0
- package/dist/scenes/title/index.js +52 -0
- package/dist/scenes/title/scene.d.ts +2 -0
- package/dist/scenes/title/scene.js +6 -0
- package/dist/scenes/title/style.css +80 -0
- package/dist/scenes/waveform/index.d.ts +1 -0
- package/dist/scenes/waveform/index.js +39 -0
- package/dist/scenes/waveform/scene.d.ts +2 -0
- package/dist/scenes/waveform/scene.js +35 -0
- package/dist/scenes/waveform/style.css +44 -0
- package/dist/shared/fit-text.d.ts +24 -0
- package/dist/shared/fit-text.js +82 -0
- package/dist/shared/format.d.ts +24 -0
- package/dist/shared/format.js +59 -0
- package/dist/shared/frame.d.ts +14 -0
- package/dist/shared/frame.js +21 -0
- package/dist/shared/icon-search.d.ts +9 -0
- package/dist/shared/icon-search.js +26 -0
- package/dist/shared/icon.d.ts +15 -0
- package/dist/shared/icon.js +28 -0
- package/dist/shared/index.d.ts +11 -0
- package/dist/shared/index.js +7 -0
- package/dist/shared/markup.d.ts +12 -0
- package/dist/shared/markup.js +43 -0
- package/dist/shared/measure.d.ts +7 -0
- package/dist/shared/measure.js +34 -0
- package/dist/shared/motion.d.ts +94 -0
- package/dist/shared/motion.js +117 -0
- package/dist/source-hash.d.ts +4 -0
- package/dist/source-hash.js +26 -0
- package/dist/test-fixtures.d.ts +9 -0
- package/dist/test-fixtures.js +22 -0
- package/dist/text-file.d.ts +1 -0
- package/dist/text-file.js +3 -0
- package/dist/words.d.ts +11 -0
- package/dist/words.js +93 -0
- package/package.json +36 -3
package/dist/registry.js
ADDED
|
@@ -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;
|