@twick/visualizer 0.0.1 → 0.14.2

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 (53) hide show
  1. package/.eslintrc.json +20 -20
  2. package/README.md +12 -12
  3. package/package.json +34 -31
  4. package/package.json.bak +34 -0
  5. package/src/animations/blur.tsx +60 -0
  6. package/src/animations/breathe.tsx +60 -0
  7. package/src/animations/fade.tsx +60 -0
  8. package/src/animations/photo-rise.tsx +66 -0
  9. package/src/animations/photo-zoom.tsx +73 -0
  10. package/src/animations/rise.tsx +118 -0
  11. package/src/animations/succession.tsx +77 -0
  12. package/src/components/frame-effects.tsx +188 -190
  13. package/src/components/track.tsx +237 -0
  14. package/src/controllers/animation.controller.ts +39 -0
  15. package/src/controllers/element.controller.ts +43 -0
  16. package/src/controllers/frame-effect.controller.tsx +30 -0
  17. package/src/controllers/text-effect.controller.ts +33 -0
  18. package/src/elements/audio.element.tsx +17 -0
  19. package/src/elements/caption.element.tsx +87 -0
  20. package/src/elements/circle.element.tsx +20 -0
  21. package/src/elements/icon.element.tsx +20 -0
  22. package/src/elements/image.element.tsx +53 -0
  23. package/src/elements/rect.element.tsx +22 -0
  24. package/src/elements/scene.element.tsx +29 -0
  25. package/src/elements/text.element.tsx +28 -0
  26. package/src/elements/video.element.tsx +55 -0
  27. package/src/frame-effects/circle.frame.tsx +103 -0
  28. package/src/frame-effects/rect.frame.tsx +103 -0
  29. package/src/global.css +11 -11
  30. package/src/helpers/caption.utils.ts +30 -40
  31. package/src/helpers/constants.ts +162 -158
  32. package/src/helpers/element.utils.ts +239 -85
  33. package/src/helpers/event.utils.ts +6 -0
  34. package/src/helpers/filters.ts +127 -127
  35. package/src/helpers/log.utils.ts +29 -32
  36. package/src/helpers/timing.utils.ts +110 -129
  37. package/src/helpers/types.ts +242 -146
  38. package/src/helpers/utils.ts +20 -0
  39. package/src/index.ts +6 -4
  40. package/src/live.tsx +16 -16
  41. package/src/project.ts +6 -6
  42. package/src/sample.ts +247 -449
  43. package/src/text-effects/elastic.tsx +39 -0
  44. package/src/text-effects/erase.tsx +58 -0
  45. package/src/text-effects/stream-word.tsx +60 -0
  46. package/src/text-effects/typewriter.tsx +59 -0
  47. package/src/visualizer.tsx +98 -78
  48. package/tsconfig.json +11 -10
  49. package/typedoc.json +14 -14
  50. package/vite.config.ts +15 -15
  51. package/src/components/animation.tsx +0 -7
  52. package/src/components/element.tsx +0 -344
  53. package/src/components/timeline.tsx +0 -225
package/.eslintrc.json CHANGED
@@ -1,21 +1,21 @@
1
- {
2
- "root": true,
3
- "extends": [
4
- "eslint:recommended",
5
- "plugin:@typescript-eslint/recommended"
6
- ],
7
- "parser": "@typescript-eslint/parser",
8
- "plugins": ["@typescript-eslint"],
9
- "parserOptions": {
10
- "ecmaVersion": "latest",
11
- "sourceType": "module"
12
- },
13
- "env": {
14
- "es2022": true,
15
- "node": true
16
- },
17
- "rules": {
18
- "@typescript-eslint/no-explicit-any": "warn",
19
- "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
20
- }
1
+ {
2
+ "root": true,
3
+ "extends": [
4
+ "eslint:recommended",
5
+ "plugin:@typescript-eslint/recommended"
6
+ ],
7
+ "parser": "@typescript-eslint/parser",
8
+ "plugins": ["@typescript-eslint"],
9
+ "parserOptions": {
10
+ "ecmaVersion": "latest",
11
+ "sourceType": "module"
12
+ },
13
+ "env": {
14
+ "es2022": true,
15
+ "node": true
16
+ },
17
+ "rules": {
18
+ "@typescript-eslint/no-explicit-any": "warn",
19
+ "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
20
+ }
21
21
  }
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
- # @twick/visualizer
2
-
3
- A visualization library built on top of [@revideo/2d](https://github.com/re-video/2d) for creating interactive visualizations.
4
-
5
- ## Installation
6
- ```
7
- pnpm install
8
- ```
9
-
10
- ## Build
11
- ```
12
- pnpm build
1
+ # @twick/visualizer
2
+
3
+ A visualization library built on top of [@twick/2d](https://github.com/re-video/2d) for creating interactive visualizations.
4
+
5
+ ## Installation
6
+ ```
7
+ pnpm install
8
+ ```
9
+
10
+ ## Build
11
+ ```
12
+ pnpm build
13
13
  ```
package/package.json CHANGED
@@ -1,31 +1,34 @@
1
- {
2
- "name": "@twick/visualizer",
3
- "version": "0.0.1",
4
- "license": "Apache-2.0",
5
- "scripts": {
6
- "start": "revideo editor --projectFile ./src/live.tsx",
7
- "build": "tsc && vite build",
8
- "docs": "typedoc --out docs src/index.ts",
9
- "clean": "rimraf .turbo node_modules dist"
10
- },
11
- "dependencies": {
12
- "@preact/signals": "^1.2.1",
13
- "@revideo/2d": "^0.10.4",
14
- "@revideo/core": "^0.10.4",
15
- "@revideo/renderer": "^0.10.4",
16
- "@revideo/vite-plugin": "^0.10.4",
17
- "date-fns": "^4.1.0",
18
- "preact": "^10.19.2",
19
- "crelt": "^1.0.6",
20
- "@twick/media-utils": "0.0.1"
21
- },
22
- "devDependencies": {
23
- "@revideo/cli": "0.10.4",
24
- "@revideo/ui": "0.10.4",
25
- "typescript": "5.4.2",
26
- "typedoc": "^0.25.8",
27
- "typedoc-plugin-markdown": "^3.17.1",
28
- "vite-plugin-dts": "^3.7.3",
29
- "vite": "^5.1.4"
30
- }
31
- }
1
+ {
2
+ "name": "@twick/visualizer",
3
+ "version": "0.14.2",
4
+ "license": "Apache-2.0",
5
+ "scripts": {
6
+ "start": "twick editor --projectFile ./src/live.tsx",
7
+ "build": "tsc && vite build",
8
+ "docs": "typedoc --out docs src/index.ts",
9
+ "clean": "rimraf .turbo node_modules dist"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "dependencies": {
15
+ "@preact/signals": "^1.2.1",
16
+ "@twick/2d": "0.14.2",
17
+ "@twick/core": "0.14.2",
18
+ "@twick/renderer": "0.14.2",
19
+ "@twick/vite-plugin": "0.14.2",
20
+ "date-fns": "^4.1.0",
21
+ "preact": "^10.19.2",
22
+ "crelt": "^1.0.6",
23
+ "@twick/media-utils": "0.14.2"
24
+ },
25
+ "devDependencies": {
26
+ "@twick/cli": "0.14.0",
27
+ "@twick/ui": "0.14.0",
28
+ "typescript": "5.4.2",
29
+ "typedoc": "^0.25.8",
30
+ "typedoc-plugin-markdown": "^3.17.1",
31
+ "vite-plugin-dts": "^3.7.3",
32
+ "vite": "^5.1.4"
33
+ }
34
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@twick/visualizer",
3
+ "version": "0.14.2",
4
+ "license": "Apache-2.0",
5
+ "scripts": {
6
+ "start": "twick editor --projectFile ./src/live.tsx",
7
+ "build": "tsc && vite build",
8
+ "docs": "typedoc --out docs src/index.ts",
9
+ "clean": "rimraf .turbo node_modules dist"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "dependencies": {
15
+ "@preact/signals": "^1.2.1",
16
+ "@twick/2d": "0.14.2",
17
+ "@twick/core": "0.14.2",
18
+ "@twick/renderer": "0.14.2",
19
+ "@twick/vite-plugin": "0.14.2",
20
+ "date-fns": "^4.1.0",
21
+ "preact": "^10.19.2",
22
+ "crelt": "^1.0.6",
23
+ "@twick/media-utils": "0.14.2"
24
+ },
25
+ "devDependencies": {
26
+ "@twick/cli": "0.14.0",
27
+ "@twick/ui": "0.14.0",
28
+ "typescript": "5.4.2",
29
+ "typedoc": "^0.25.8",
30
+ "typedoc-plugin-markdown": "^3.17.1",
31
+ "vite-plugin-dts": "^3.7.3",
32
+ "vite": "^5.1.4"
33
+ }
34
+ }
@@ -0,0 +1,60 @@
1
+ import { waitFor } from "@twick/core";
2
+ import { AnimationParams } from "../helpers/types";
3
+
4
+ /**
5
+ * BlurAnimation applies a blur effect to an element or its container
6
+ * during enter, exit, or both animations.
7
+ *
8
+ * Available animation modes:
9
+ * - "enter": Starts blurred and gradually becomes clear.
10
+ * - "exit": Starts clear and gradually becomes blurred.
11
+ * - "both": Blurs in, clears, then blurs out.
12
+ *
13
+ * @param elementRef - Reference to the main element.
14
+ * @param containerRef - Optional reference to a container element.
15
+ * @param interval - Duration (in frames or ms) of each blur transition.
16
+ * @param duration - Total duration of the animation.
17
+ * @param intensity - Maximum blur strength (default: 25).
18
+ * @param animate - Animation phase ("enter" | "exit" | "both").
19
+ */
20
+ export const BlurAnimation = {
21
+ name: "blur",
22
+
23
+ /**
24
+ * Generator function controlling the blur animation.
25
+ */
26
+ *run({
27
+ elementRef,
28
+ containerRef,
29
+ interval,
30
+ duration,
31
+ intensity = 25,
32
+ animate,
33
+ }: AnimationParams) {
34
+ // Choose containerRef if provided; otherwise, fallback to elementRef
35
+ const ref = containerRef ?? elementRef;
36
+
37
+ let animationInterval = Math.min(interval, duration);
38
+ if (animate === "enter") {
39
+ // Start fully blurred
40
+ ref().filters.blur(intensity);
41
+ // Animate to no blur over 'interval'
42
+ yield* ref().filters.blur(0, animationInterval);
43
+ } else if (animate === "exit") {
44
+ // Wait for the time before exit animation starts
45
+ yield* waitFor(duration - animationInterval);
46
+ // Animate from no blur to full blur over 'interval'
47
+ yield* ref().filters.blur(intensity, animationInterval);
48
+ } else if (animate === "both") {
49
+ animationInterval = Math.min(interval, duration/2);
50
+ // Start fully blurred
51
+ ref().filters.blur(intensity);
52
+ // Animate to no blur
53
+ yield* ref().filters.blur(0, animationInterval);
54
+ // Wait until exit animation
55
+ yield* waitFor(duration - animationInterval);
56
+ // Animate to full blur again
57
+ yield* ref().filters.blur(intensity, animationInterval);
58
+ }
59
+ },
60
+ };
@@ -0,0 +1,60 @@
1
+ import { Vector2 } from "@twick/core";
2
+ import { AnimationParams } from "../helpers/types";
3
+ import { getTimingFunction } from "../helpers/timing.utils";
4
+
5
+ /**
6
+ * BreatheAnimation applies a smooth scale in/out effect to simulate
7
+ * a "breathing" motion.
8
+ *
9
+ * Available modes:
10
+ * - "in": Gradually scales down (shrinks) to the target intensity.
11
+ * - "out": Starts scaled down, then grows back to original size.
12
+ *
13
+ * @param elementRef - Reference to the main element to animate.
14
+ * @param containerRef - Optional reference to a container element.
15
+ * @param mode - Animation phase ("in" | "out").
16
+ * @param duration - Duration of the scaling animation.
17
+ * @param intensity - Target scale factor (default: 0.5).
18
+ */
19
+ export const BreatheAnimation = {
20
+ name: "breathe",
21
+
22
+ /**
23
+ * Generator function controlling the breathing scale animation.
24
+ */
25
+ *run({
26
+ elementRef,
27
+ containerRef,
28
+ mode,
29
+ duration,
30
+ intensity = 0.5,
31
+ }: AnimationParams) {
32
+ // Use containerRef if provided, otherwise fallback to elementRef
33
+ const ref = containerRef ?? elementRef;
34
+
35
+ // Get the current scale of the element
36
+ const scale = ref().scale();
37
+
38
+ if (mode === "in") {
39
+ // Animate scaling down to (original scale * intensity)
40
+ yield* ref().scale(
41
+ new Vector2(scale.x * intensity, scale.y * intensity),
42
+ duration,
43
+ getTimingFunction("easeInSine")
44
+ );
45
+ }
46
+
47
+ if (mode === "out") {
48
+ // Immediately set to scaled down size
49
+ ref().scale(
50
+ new Vector2(scale.x * intensity, scale.y * intensity)
51
+ );
52
+ // Animate scaling back up to original size
53
+ yield* ref().scale(
54
+ new Vector2(scale.x, scale.y),
55
+ duration,
56
+ getTimingFunction("easeOutSine")
57
+ );
58
+ }
59
+ },
60
+ };
@@ -0,0 +1,60 @@
1
+ import { waitFor } from "@twick/core";
2
+ import { AnimationParams } from "../helpers/types";
3
+
4
+ /**
5
+ * FadeAnimation applies a simple fade-in and fade-out effect
6
+ * by adjusting opacity.
7
+ *
8
+ * Available animation modes:
9
+ * - "enter": Starts transparent and fades in to fully opaque.
10
+ * - "exit": Waits, then fades out to transparent.
11
+ * - "both": Fades in, waits, then fades out.
12
+ *
13
+ * @param elementRef - Reference to the main element to animate.
14
+ * @param containerRef - Optional reference to a container element.
15
+ * @param interval - Duration of the fade transition (in frames or ms).
16
+ * @param duration - Total duration of the animation.
17
+ * @param animate - Animation phase ("enter" | "exit" | "both").
18
+ */
19
+ export const FadeAnimation = {
20
+ name: "fade",
21
+
22
+ /**
23
+ * Generator function controlling the fade animation.
24
+ */
25
+ *run({
26
+ elementRef,
27
+ containerRef,
28
+ interval,
29
+ duration,
30
+ animate,
31
+ }: AnimationParams) {
32
+ // Use containerRef if provided, otherwise fallback to elementRef
33
+ const ref = containerRef ?? elementRef;
34
+
35
+ let animationInterval = Math.min(interval, duration);
36
+ if (animate === "enter") {
37
+ // Start fully transparent
38
+ ref().opacity(0);
39
+ // Fade in to full opacity over 'interval'
40
+ yield* ref().opacity(1, animationInterval);
41
+
42
+ } else if (animate === "exit") {
43
+ // Wait until it's time to start fading out
44
+ yield* waitFor(duration - animationInterval);
45
+ // Fade out to transparent over 'interval'
46
+ yield* ref().opacity(0, animationInterval);
47
+
48
+ } else if (animate === "both") {
49
+ animationInterval = Math.min(interval, duration/2);
50
+ // Start fully transparent
51
+ ref().opacity(0);
52
+ // Fade in to full opacity
53
+ yield* ref().opacity(1, animationInterval);
54
+ // Wait for the remaining duration before fade-out
55
+ yield* waitFor(duration - animationInterval);
56
+ // Fade out to transparent
57
+ yield* ref().opacity(0, animationInterval);
58
+ }
59
+ },
60
+ };
@@ -0,0 +1,66 @@
1
+ import { AnimationParams } from "../helpers/types";
2
+
3
+ /**
4
+ * PhotoRiseAnimation applies a smooth directional movement to a photo element.
5
+ *
6
+ * Behavior:
7
+ * - Starts offset in a given direction (up, down, left, or right).
8
+ * - Animates back to the original position over the specified duration.
9
+ *
10
+ * Available directions:
11
+ * - "up": Starts below and moves upward.
12
+ * - "down": Starts above and moves downward.
13
+ * - "left": Starts to the right and moves leftward.
14
+ * - "right": Starts to the left and moves rightward.
15
+ *
16
+ * @param elementRef - Reference to the photo element to animate.
17
+ * @param containerRef - Optional reference to a container element (required for this animation).
18
+ * @param direction - Direction of the movement ("up" | "down" | "left" | "right").
19
+ * @param duration - Duration of the movement animation.
20
+ * @param intensity - Offset distance in pixels (default: 200).
21
+ */
22
+ export const PhotoRiseAnimation = {
23
+ name: "photo-rise",
24
+
25
+ /**
26
+ * Generator function controlling the photo rise animation.
27
+ */
28
+ *run({
29
+ elementRef,
30
+ containerRef,
31
+ direction,
32
+ duration,
33
+ intensity = 200,
34
+ }: AnimationParams) {
35
+ // Only run if a containerRef is provided
36
+ if (containerRef) {
37
+ // Get the element's current position
38
+ const pos = elementRef().position();
39
+
40
+ if (direction === "up") {
41
+ // Start offset below
42
+ elementRef().y(pos.y + intensity);
43
+ // Animate moving upward to the original position
44
+ yield* elementRef().y(pos.y, duration);
45
+
46
+ } else if (direction === "down") {
47
+ // Start offset above
48
+ elementRef().y(pos.y - intensity);
49
+ // Animate moving downward to the original position
50
+ yield* elementRef().y(pos.y, duration);
51
+
52
+ } else if (direction === "left") {
53
+ // Start offset to the right
54
+ elementRef().x(pos.x + intensity);
55
+ // Animate moving left to the original position
56
+ yield* elementRef().x(pos.x, duration);
57
+
58
+ } else if (direction === "right") {
59
+ // Start offset to the left
60
+ elementRef().x(pos.x - intensity);
61
+ // Animate moving right to the original position
62
+ yield* elementRef().x(pos.x, duration);
63
+ }
64
+ }
65
+ },
66
+ };
@@ -0,0 +1,73 @@
1
+ import { Vector2 } from "@twick/core";
2
+ import { AnimationParams } from "../helpers/types";
3
+
4
+ /**
5
+ * PhotoZoomAnimation applies a smooth zoom-in or zoom-out effect
6
+ * on a photo element.
7
+ *
8
+ * Available animation modes:
9
+ * - "in": Starts zoomed in and smoothly scales back to the original size.
10
+ * - "out": Starts at normal size and smoothly scales up to the target zoom level.
11
+ *
12
+ * @param elementRef - Reference to the photo element to animate.
13
+ * @param containerRef - Optional reference to a container element (required for this animation).
14
+ * @param mode - Animation phase ("in" | "out").
15
+ * @param duration - Duration of the zoom animation.
16
+ * @param intensity - Zoom scale multiplier (default: 1.5).
17
+ */
18
+ export const PhotoZoomAnimation = {
19
+ name: "photo-zoom",
20
+
21
+ /**
22
+ * Generator function controlling the photo zoom animation.
23
+ */
24
+ *run({
25
+ elementRef,
26
+ containerRef,
27
+ mode,
28
+ duration,
29
+ intensity = 1.5,
30
+ }: AnimationParams) {
31
+ // Only run if a containerRef is provided
32
+ if (containerRef) {
33
+ // Get the element's current scale
34
+ const elementScale = elementRef().scale();
35
+
36
+ if (mode === "in") {
37
+ // Instantly set to zoomed-in scale
38
+ yield elementRef().scale(
39
+ new Vector2(
40
+ elementScale.x * intensity,
41
+ elementScale.y * intensity
42
+ )
43
+ );
44
+ // Smoothly scale back to original size over 'duration'
45
+ yield* elementRef().scale(
46
+ new Vector2(
47
+ elementScale.x,
48
+ elementScale.y
49
+ ),
50
+ duration
51
+ );
52
+ }
53
+
54
+ if (mode === "out") {
55
+ // Start at original scale
56
+ elementRef().scale(
57
+ new Vector2(
58
+ elementScale.x,
59
+ elementScale.y
60
+ )
61
+ );
62
+ // Smoothly scale up to zoomed-in scale over 'duration'
63
+ yield* elementRef().scale(
64
+ new Vector2(
65
+ elementScale.x * intensity,
66
+ elementScale.y * intensity
67
+ ),
68
+ duration
69
+ );
70
+ }
71
+ }
72
+ },
73
+ };
@@ -0,0 +1,118 @@
1
+ import { all, waitFor, delay } from "@twick/core";
2
+ import { AnimationParams } from "../helpers/types";
3
+
4
+ /**
5
+ * RiseAnimation combines vertical motion and opacity transitions
6
+ * to create a "rising" (or "falling") appearance/disappearance effect.
7
+ *
8
+ * Available animation modes:
9
+ * - "enter": Starts offset and transparent, moves into position while fading in.
10
+ * - "exit": Waits, then moves out of position while fading out.
11
+ * - "both": Enters, waits, and exits in a continuous sequence.
12
+ *
13
+ * @param elementRef - Reference to the main element to animate.
14
+ * @param containerRef - Optional reference to a container element.
15
+ * @param interval - Duration of movement and opacity transitions (default: 0.25).
16
+ * @param duration - Total duration of the animation.
17
+ * @param animate - Animation phase ("enter" | "exit" | "both").
18
+ * @param direction - Direction to animate ("up" or "down").
19
+ * @param intensity - Number of units to offset position vertically (default: 200).
20
+ */
21
+ export const RiseAnimation = {
22
+ name: "rise",
23
+
24
+ /**
25
+ * Generator function controlling the rise/fall animation.
26
+ */
27
+ *run({
28
+ elementRef,
29
+ containerRef,
30
+ interval = 0.25,
31
+ duration,
32
+ animate,
33
+ direction,
34
+ intensity = 200,
35
+ }: AnimationParams) {
36
+ // Use containerRef if provided, otherwise fallback to elementRef
37
+ const ref = containerRef ?? elementRef;
38
+
39
+ // Get the element's current position
40
+ const pos = ref().position();
41
+
42
+ let animationInterval = Math.min(interval, duration);
43
+
44
+ if (animate === "enter") {
45
+ // Start fully transparent
46
+ ref().opacity(0);
47
+
48
+ if (direction === "up") {
49
+ // Offset element below final position
50
+ ref().y(pos.y + intensity);
51
+ // Animate moving up while fading in
52
+ yield* all(
53
+ ref().opacity(1, animationInterval / 4),
54
+ ref().y(pos.y, animationInterval)
55
+ );
56
+ } else if (direction === "down") {
57
+ // Offset element above final position
58
+ ref().y(pos.y - intensity);
59
+ // Animate moving down while fading in
60
+ yield* all(
61
+ ref().opacity(1, animationInterval / 4),
62
+ ref().y(pos.y, animationInterval)
63
+ );
64
+ }
65
+ } else if (animate === "exit") {
66
+ // Wait until exit animation starts
67
+ yield* waitFor(duration - animationInterval);
68
+
69
+ if (direction === "up") {
70
+ // Animate moving up while fading out (opacity fades slightly after motion starts)
71
+ yield* all(
72
+ delay((3 * animationInterval) / 4, ref().opacity(0, animationInterval / 4)),
73
+ ref().y(pos.y - intensity, animationInterval)
74
+ );
75
+ } else if (direction === "down") {
76
+ // Animate moving down while fading out
77
+ yield* all(
78
+ delay((3 * animationInterval) / 4, ref().opacity(0, animationInterval / 4)),
79
+ ref().y(pos.y + intensity, animationInterval)
80
+ );
81
+ }
82
+ } else if (animate === "both") {
83
+ animationInterval = Math.min(interval, duration/2);
84
+ // Start fully transparent
85
+ ref().opacity(0);
86
+
87
+ if (direction === "up") {
88
+ // Enter animation: move up while fading in
89
+ ref().y(pos.y + intensity);
90
+ yield* all(
91
+ ref().opacity(1, animationInterval / 4),
92
+ ref().y(pos.y, animationInterval)
93
+ );
94
+ // Wait for the remaining duration
95
+ yield* waitFor(duration - animationInterval);
96
+ // Exit animation: move up further while fading out
97
+ yield* all(
98
+ delay((3 * animationInterval) / 4, ref().opacity(0, animationInterval / 4)),
99
+ ref().y(pos.y - intensity, animationInterval)
100
+ );
101
+ } else if (direction === "down") {
102
+ // Enter animation: move down while fading in
103
+ ref().y(pos.y - intensity);
104
+ yield* all(
105
+ ref().opacity(1, animationInterval / 4),
106
+ ref().y(pos.y, animationInterval)
107
+ );
108
+ // Wait for the remaining duration
109
+ yield* waitFor(duration - animationInterval);
110
+ // Exit animation: move down further while fading out
111
+ yield* all(
112
+ delay((3 * animationInterval) / 4, ref().opacity(0, animationInterval / 4)),
113
+ ref().y(pos.y + intensity, animationInterval)
114
+ );
115
+ }
116
+ }
117
+ },
118
+ };