@slithy/math-particles 0.1.1 → 0.2.0

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @slithy/math-particles
2
2
 
3
- Animated math equations particle overlay for React. Equations drift outward from the center of the container, scaling and fading over time. No animation library — driven entirely by a `requestAnimationFrame` loop with hand-rolled easing.
3
+ Animated math equations particle overlay for React. Equations drift outward from the center of the container, scaling and fading over time. No animation library — the loop runs in a Web Worker via `OffscreenCanvas`, keeping the main thread free.
4
4
 
5
5
  ## Installation
6
6
 
@@ -29,9 +29,13 @@ import { MathParticlesOverlay } from '@slithy/math-particles'
29
29
  | Prop | Type | Default | Description |
30
30
  |---|---|---|---|
31
31
  | `backgroundColor` | `string` | `"#07070f"` | Background fill color of the container |
32
+ | `blur` | `boolean` | `true` | Applies a soft blur at the start and end of each particle's lifecycle |
32
33
  | `colors` | `string[]` | ColorBrewer RdYlBu | Array of colors to pick from randomly per particle |
33
34
  | `density` | `number` | `1` | Multiplier on the auto-calculated particle count. `0.5` = half, `2` = double. Fixed at mount — use a `key` prop to reinitialize. |
35
+ | `fadeIn` | `boolean` | `true` | Fades the canvas in on mount |
36
+ | `fadeInDuration` | `number` | `800` | Duration of the mount fade-in in milliseconds |
34
37
  | `reverse` | `boolean` | `false` | Reverses the animation: equations start large and drift inward rather than starting small and drifting outward |
38
+ | `speed` | `number` | `1` | Multiplier on particle animation speed. `0.5` = half speed, `2` = double. |
35
39
 
36
40
  ---
37
41
 
@@ -55,6 +59,18 @@ import { MathParticlesOverlay } from '@slithy/math-particles'
55
59
  <MathParticlesOverlay reverse />
56
60
  ```
57
61
 
62
+ ### No blur
63
+
64
+ ```tsx
65
+ <MathParticlesOverlay blur={false} />
66
+ ```
67
+
68
+ ### No fade-in
69
+
70
+ ```tsx
71
+ <MathParticlesOverlay fadeIn={false} />
72
+ ```
73
+
58
74
  ### Transparent background
59
75
 
60
76
  ```tsx
@@ -0,0 +1,206 @@
1
+ // src/equations.ts
2
+ var EQUATIONS = [
3
+ {
4
+ label: "euler",
5
+ w: 220,
6
+ h: 60,
7
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 60" width="220" height="60">
8
+ <text x="10" y="44" font-family="Georgia,serif" font-size="38" fill="currentColor">
9
+ <tspan font-style="italic">e</tspan>
10
+ <tspan font-size="22" baseline-shift="super">i&#x3C0;</tspan>
11
+ <tspan> + 1 = 0</tspan>
12
+ </text>
13
+ </svg>`
14
+ },
15
+ {
16
+ label: "pythagoras",
17
+ w: 230,
18
+ h: 60,
19
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 230 60" width="230" height="60">
20
+ <text x="10" y="44" font-family="Georgia,serif" font-size="38" fill="currentColor">
21
+ <tspan font-style="italic">a</tspan><tspan font-size="22" baseline-shift="super">2</tspan>
22
+ <tspan> + </tspan>
23
+ <tspan font-style="italic">b</tspan><tspan font-size="22" baseline-shift="super">2</tspan>
24
+ <tspan> = </tspan>
25
+ <tspan font-style="italic">c</tspan><tspan font-size="22" baseline-shift="super">2</tspan>
26
+ </text>
27
+ </svg>`
28
+ },
29
+ {
30
+ label: "quadratic",
31
+ w: 310,
32
+ h: 80,
33
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 310 80" width="310" height="80">
34
+ <text x="10" y="32" font-family="Georgia,serif" font-size="22" fill="currentColor">
35
+ <tspan font-style="italic">x</tspan><tspan> = </tspan>
36
+ <tspan>&#x2212;</tspan><tspan font-style="italic">b</tspan>
37
+ <tspan> &#xB1; </tspan>
38
+ <tspan>&#x221A;(</tspan><tspan font-style="italic">b</tspan>
39
+ <tspan font-size="14" baseline-shift="super">2</tspan>
40
+ <tspan> &#x2212; 4</tspan><tspan font-style="italic">ac</tspan><tspan>)</tspan>
41
+ </text>
42
+ <line x1="127" y1="40" x2="295" y2="40" stroke="currentColor" stroke-width="1.5"/>
43
+ <text x="195" y="65" font-family="Georgia,serif" font-size="22" fill="currentColor">2<tspan font-style="italic">a</tspan></text>
44
+ </svg>`
45
+ },
46
+ {
47
+ label: "einstein",
48
+ w: 180,
49
+ h: 60,
50
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 60" width="180" height="60">
51
+ <text x="10" y="44" font-family="Georgia,serif" font-size="38" fill="currentColor">
52
+ <tspan font-style="italic">E</tspan>
53
+ <tspan> = </tspan>
54
+ <tspan font-style="italic">mc</tspan>
55
+ <tspan font-size="22" baseline-shift="super">2</tspan>
56
+ </text>
57
+ </svg>`
58
+ },
59
+ {
60
+ label: "fourier",
61
+ w: 330,
62
+ h: 80,
63
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 330 80" width="330" height="80">
64
+ <text x="10" y="32" font-family="Georgia,serif" font-size="20" fill="currentColor">
65
+ <tspan font-style="italic">F</tspan><tspan>(&#x03BE;) = </tspan>
66
+ </text>
67
+ <text x="90" y="22" font-family="Georgia,serif" font-size="30" fill="currentColor">&#x222B;</text>
68
+ <text x="67" y="22" font-family="Georgia,serif" font-size="13" fill="currentColor">+&#x221E;</text>
69
+ <text x="67" y="38" font-family="Georgia,serif" font-size="13" fill="currentColor">&#x2212;&#x221E;</text>
70
+ <text x="115" y="32" font-family="Georgia,serif" font-size="20" fill="currentColor">
71
+ <tspan font-style="italic">f</tspan><tspan>(</tspan><tspan font-style="italic">x</tspan><tspan>)</tspan>
72
+ <tspan font-style="italic"> e</tspan>
73
+ <tspan font-size="13" baseline-shift="super">&#x2212;2&#x3C0;i&#x03BE;</tspan>
74
+ <tspan font-style="italic" font-size="13" baseline-shift="super">x</tspan>
75
+ <tspan> d</tspan><tspan font-style="italic">x</tspan>
76
+ </text>
77
+ </svg>`
78
+ },
79
+ {
80
+ label: "schrodinger",
81
+ w: 220,
82
+ h: 80,
83
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 80" width="220" height="80">
84
+ <text x="10" y="38" font-family="Georgia,serif" font-size="24" fill="currentColor">
85
+ <tspan font-style="italic">i</tspan><tspan>&#x210F;</tspan>
86
+ </text>
87
+ <text x="44" y="26" font-family="Georgia,serif" font-size="20" fill="currentColor">&#x2202;&#x3A8;</text>
88
+ <line x1="42" y1="32" x2="80" y2="32" stroke="currentColor" stroke-width="1.4"/>
89
+ <text x="46" y="52" font-family="Georgia,serif" font-size="20" fill="currentColor">&#x2202;<tspan font-style="italic">t</tspan></text>
90
+ <text x="86" y="38" font-family="Georgia,serif" font-size="24" fill="currentColor"> = &#x0124;&#x3A8;</text>
91
+ </svg>`
92
+ },
93
+ {
94
+ label: "gaussian",
95
+ w: 240,
96
+ h: 80,
97
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 80" width="240" height="80">
98
+ <text x="22" y="22" font-family="Georgia,serif" font-size="30" fill="currentColor">&#x222B;</text>
99
+ <text x="10" y="20" font-family="Georgia,serif" font-size="13" fill="currentColor">+&#x221E;</text>
100
+ <text x="10" y="48" font-family="Georgia,serif" font-size="13" fill="currentColor">&#x2212;&#x221E;</text>
101
+ <text x="50" y="38" font-family="Georgia,serif" font-size="24" fill="currentColor">
102
+ <tspan font-style="italic">e</tspan>
103
+ <tspan font-size="15" baseline-shift="super">&#x2212;<tspan font-style="italic">x</tspan><tspan font-size="10">2</tspan></tspan>
104
+ <tspan> d</tspan><tspan font-style="italic">x</tspan><tspan> = &#x221A;&#x3C0;</tspan>
105
+ </text>
106
+ </svg>`
107
+ },
108
+ {
109
+ label: "taylor",
110
+ w: 320,
111
+ h: 80,
112
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 80" width="320" height="80">
113
+ <text x="10" y="38" font-family="Georgia,serif" font-size="22" fill="currentColor">
114
+ <tspan font-style="italic">f</tspan><tspan>(</tspan><tspan font-style="italic">x</tspan><tspan>) = </tspan>
115
+ </text>
116
+ <text x="90" y="18" font-family="Georgia,serif" font-size="15" fill="currentColor">&#x221E;</text>
117
+ <text x="88" y="36" font-family="Georgia,serif" font-size="28" fill="currentColor">&#x2211;</text>
118
+ <text x="83" y="56" font-family="Georgia,serif" font-size="14" fill="currentColor"><tspan font-style="italic">n</tspan>=0</text>
119
+ <text x="126" y="26" font-family="Georgia,serif" font-size="17" fill="currentColor">
120
+ <tspan font-style="italic">f</tspan><tspan font-size="12" baseline-shift="super">(<tspan font-style="italic">n</tspan>)</tspan>(<tspan font-style="italic">a</tspan>)
121
+ </text>
122
+ <line x1="124" y1="32" x2="178" y2="32" stroke="currentColor" stroke-width="1.2"/>
123
+ <text x="138" y="52" font-family="Georgia,serif" font-size="17" fill="currentColor">
124
+ <tspan font-style="italic">n</tspan><tspan>!</tspan>
125
+ </text>
126
+ <text x="184" y="38" font-family="Georgia,serif" font-size="22" fill="currentColor">
127
+ (<tspan font-style="italic">x&#x2212;a</tspan>)<tspan font-size="14" baseline-shift="super"><tspan font-style="italic">n</tspan></tspan>
128
+ </text>
129
+ </svg>`
130
+ },
131
+ {
132
+ label: "navier-stokes",
133
+ w: 370,
134
+ h: 60,
135
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 370 60" width="370" height="60">
136
+ <text x="10" y="40" font-family="Georgia,serif" font-size="22" fill="currentColor">
137
+ &#x03C1;(&#x2202;<tspan font-style="italic">t</tspan><tspan font-weight="bold" font-style="italic">u</tspan> + <tspan font-style="italic">u</tspan>&#x22C5;&#x2207;<tspan font-style="italic">u</tspan>) = &#x2212;&#x2207;<tspan font-style="italic">p</tspan> + &#x03BC;&#x2207;<tspan font-size="16" baseline-shift="super">2</tspan><tspan font-weight="bold" font-style="italic">u</tspan>
138
+ </text>
139
+ </svg>`
140
+ },
141
+ {
142
+ label: "maxwell",
143
+ w: 210,
144
+ h: 70,
145
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210 70" width="210" height="70">
146
+ <text x="10" y="44" font-family="Georgia,serif" font-size="30" fill="currentColor">
147
+ &#x2207; &#x22C5; <tspan font-weight="bold" font-style="italic">E</tspan> =
148
+ </text>
149
+ <text x="136" y="32" font-family="Georgia,serif" font-size="22" fill="currentColor">&#x03C1;</text>
150
+ <line x1="134" y1="38" x2="165" y2="38" stroke="currentColor" stroke-width="1.4"/>
151
+ <text x="136" y="58" font-family="Georgia,serif" font-size="22" fill="currentColor">&#x03B5;<tspan font-size="14" baseline-shift="sub">0</tspan></text>
152
+ </svg>`
153
+ },
154
+ {
155
+ label: "riemann",
156
+ w: 200,
157
+ h: 80,
158
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80" width="200" height="80">
159
+ <text x="10" y="38" font-family="Georgia,serif" font-size="22" fill="currentColor">
160
+ &#x03B6;(<tspan font-style="italic">s</tspan>) =
161
+ </text>
162
+ <text x="85" y="18" font-family="Georgia,serif" font-size="15" fill="currentColor">&#x221E;</text>
163
+ <text x="82" y="36" font-family="Georgia,serif" font-size="28" fill="currentColor">&#x2211;</text>
164
+ <text x="78" y="56" font-family="Georgia,serif" font-size="14" fill="currentColor"><tspan font-style="italic">n</tspan>=1</text>
165
+ <text x="120" y="26" font-family="Georgia,serif" font-size="18" fill="currentColor">1</text>
166
+ <line x1="118" y1="32" x2="152" y2="32" stroke="currentColor" stroke-width="1.2"/>
167
+ <text x="120" y="52" font-family="Georgia,serif" font-size="18" fill="currentColor">
168
+ <tspan font-style="italic">n</tspan><tspan font-size="12" baseline-shift="super"><tspan font-style="italic">s</tspan></tspan>
169
+ </text>
170
+ </svg>`
171
+ },
172
+ {
173
+ label: "bayes",
174
+ w: 300,
175
+ h: 80,
176
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 80" width="300" height="80">
177
+ <text x="10" y="36" font-family="Georgia,serif" font-size="22" fill="currentColor">
178
+ <tspan font-style="italic">P</tspan>(<tspan font-style="italic">A</tspan>|<tspan font-style="italic">B</tspan>) =
179
+ </text>
180
+ <text x="118" y="24" font-family="Georgia,serif" font-size="20" fill="currentColor">
181
+ <tspan font-style="italic">P</tspan>(<tspan font-style="italic">B</tspan>|<tspan font-style="italic">A</tspan>)<tspan font-style="italic">P</tspan>(<tspan font-style="italic">A</tspan>)
182
+ </text>
183
+ <line x1="116" y1="32" x2="258" y2="32" stroke="currentColor" stroke-width="1.3"/>
184
+ <text x="162" y="56" font-family="Georgia,serif" font-size="20" fill="currentColor">
185
+ <tspan font-style="italic">P</tspan>(<tspan font-style="italic">B</tspan>)
186
+ </text>
187
+ </svg>`
188
+ }
189
+ ];
190
+ var DEFAULT_COLORS = [
191
+ "#313695",
192
+ "#4575b4",
193
+ "#74add1",
194
+ "#abd9e9",
195
+ "#fee090",
196
+ "#fdae61",
197
+ "#f46d43",
198
+ "#d73027",
199
+ "#a50026",
200
+ "#ffffff"
201
+ ];
202
+
203
+ export {
204
+ EQUATIONS,
205
+ DEFAULT_COLORS
206
+ };
package/dist/index.d.ts CHANGED
@@ -2,11 +2,14 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
 
3
3
  type Props = {
4
4
  backgroundColor?: string;
5
+ blur?: boolean;
5
6
  colors?: string[];
6
7
  density?: number;
7
8
  reverse?: boolean;
8
- transitionDuration?: number;
9
+ speed?: number;
10
+ fadeIn?: boolean;
11
+ fadeInDuration?: number;
9
12
  };
10
- declare const MathParticlesOverlay: ({ backgroundColor, colors, density, reverse, transitionDuration, }: Props) => react_jsx_runtime.JSX.Element;
13
+ declare const MathParticlesOverlay: ({ backgroundColor, blur, colors, density, reverse, speed, fadeIn, fadeInDuration, }: Props) => react_jsx_runtime.JSX.Element;
11
14
 
12
15
  export { MathParticlesOverlay };
package/dist/index.js CHANGED
@@ -1,342 +1,138 @@
1
+ import {
2
+ DEFAULT_COLORS,
3
+ EQUATIONS
4
+ } from "./chunk-7SB7KDRV.js";
5
+
1
6
  // src/MathParticlesOverlay.tsx
2
7
  import { useEffect, useRef } from "react";
3
8
  import { jsx } from "react/jsx-runtime";
4
- var EQUATIONS = [
5
- {
6
- label: "euler",
7
- w: 220,
8
- h: 60,
9
- svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 60" width="220" height="60">
10
- <text x="10" y="44" font-family="Georgia,serif" font-size="38" fill="currentColor">
11
- <tspan font-style="italic">e</tspan>
12
- <tspan font-size="22" baseline-shift="super">i&#x3C0;</tspan>
13
- <tspan> + 1 = 0</tspan>
14
- </text>
15
- </svg>`
16
- },
17
- {
18
- label: "pythagoras",
19
- w: 230,
20
- h: 60,
21
- svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 230 60" width="230" height="60">
22
- <text x="10" y="44" font-family="Georgia,serif" font-size="38" fill="currentColor">
23
- <tspan font-style="italic">a</tspan><tspan font-size="22" baseline-shift="super">2</tspan>
24
- <tspan> + </tspan>
25
- <tspan font-style="italic">b</tspan><tspan font-size="22" baseline-shift="super">2</tspan>
26
- <tspan> = </tspan>
27
- <tspan font-style="italic">c</tspan><tspan font-size="22" baseline-shift="super">2</tspan>
28
- </text>
29
- </svg>`
30
- },
31
- {
32
- label: "quadratic",
33
- w: 310,
34
- h: 80,
35
- svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 310 80" width="310" height="80">
36
- <text x="10" y="32" font-family="Georgia,serif" font-size="22" fill="currentColor">
37
- <tspan font-style="italic">x</tspan><tspan> = </tspan>
38
- <tspan>&#x2212;</tspan><tspan font-style="italic">b</tspan>
39
- <tspan> &#xB1; </tspan>
40
- <tspan>&#x221A;(</tspan><tspan font-style="italic">b</tspan>
41
- <tspan font-size="14" baseline-shift="super">2</tspan>
42
- <tspan> &#x2212; 4</tspan><tspan font-style="italic">ac</tspan><tspan>)</tspan>
43
- </text>
44
- <line x1="127" y1="40" x2="295" y2="40" stroke="currentColor" stroke-width="1.5"/>
45
- <text x="195" y="65" font-family="Georgia,serif" font-size="22" fill="currentColor">2<tspan font-style="italic">a</tspan></text>
46
- </svg>`
47
- },
48
- {
49
- label: "einstein",
50
- w: 180,
51
- h: 60,
52
- svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 60" width="180" height="60">
53
- <text x="10" y="44" font-family="Georgia,serif" font-size="38" fill="currentColor">
54
- <tspan font-style="italic">E</tspan>
55
- <tspan> = </tspan>
56
- <tspan font-style="italic">mc</tspan>
57
- <tspan font-size="22" baseline-shift="super">2</tspan>
58
- </text>
59
- </svg>`
60
- },
61
- {
62
- label: "fourier",
63
- w: 330,
64
- h: 80,
65
- svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 330 80" width="330" height="80">
66
- <text x="10" y="32" font-family="Georgia,serif" font-size="20" fill="currentColor">
67
- <tspan font-style="italic">F</tspan><tspan>(&#x03BE;) = </tspan>
68
- </text>
69
- <text x="90" y="22" font-family="Georgia,serif" font-size="30" fill="currentColor">&#x222B;</text>
70
- <text x="67" y="22" font-family="Georgia,serif" font-size="13" fill="currentColor">+&#x221E;</text>
71
- <text x="67" y="38" font-family="Georgia,serif" font-size="13" fill="currentColor">&#x2212;&#x221E;</text>
72
- <text x="115" y="32" font-family="Georgia,serif" font-size="20" fill="currentColor">
73
- <tspan font-style="italic">f</tspan><tspan>(</tspan><tspan font-style="italic">x</tspan><tspan>)</tspan>
74
- <tspan font-style="italic"> e</tspan>
75
- <tspan font-size="13" baseline-shift="super">&#x2212;2&#x3C0;i&#x03BE;</tspan>
76
- <tspan font-style="italic" font-size="13" baseline-shift="super">x</tspan>
77
- <tspan> d</tspan><tspan font-style="italic">x</tspan>
78
- </text>
79
- </svg>`
80
- },
81
- {
82
- label: "schrodinger",
83
- w: 220,
84
- h: 80,
85
- svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 80" width="220" height="80">
86
- <text x="10" y="38" font-family="Georgia,serif" font-size="24" fill="currentColor">
87
- <tspan font-style="italic">i</tspan><tspan>&#x210F;</tspan>
88
- </text>
89
- <text x="44" y="26" font-family="Georgia,serif" font-size="20" fill="currentColor">&#x2202;&#x3A8;</text>
90
- <line x1="42" y1="32" x2="80" y2="32" stroke="currentColor" stroke-width="1.4"/>
91
- <text x="46" y="52" font-family="Georgia,serif" font-size="20" fill="currentColor">&#x2202;<tspan font-style="italic">t</tspan></text>
92
- <text x="86" y="38" font-family="Georgia,serif" font-size="24" fill="currentColor"> = &#x0124;&#x3A8;</text>
93
- </svg>`
94
- },
95
- {
96
- label: "gaussian",
97
- w: 240,
98
- h: 80,
99
- svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 80" width="240" height="80">
100
- <text x="22" y="22" font-family="Georgia,serif" font-size="30" fill="currentColor">&#x222B;</text>
101
- <text x="10" y="20" font-family="Georgia,serif" font-size="13" fill="currentColor">+&#x221E;</text>
102
- <text x="10" y="48" font-family="Georgia,serif" font-size="13" fill="currentColor">&#x2212;&#x221E;</text>
103
- <text x="50" y="38" font-family="Georgia,serif" font-size="24" fill="currentColor">
104
- <tspan font-style="italic">e</tspan>
105
- <tspan font-size="15" baseline-shift="super">&#x2212;<tspan font-style="italic">x</tspan><tspan font-size="10">2</tspan></tspan>
106
- <tspan> d</tspan><tspan font-style="italic">x</tspan><tspan> = &#x221A;&#x3C0;</tspan>
107
- </text>
108
- </svg>`
109
- },
110
- {
111
- label: "taylor",
112
- w: 320,
113
- h: 80,
114
- svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 80" width="320" height="80">
115
- <text x="10" y="38" font-family="Georgia,serif" font-size="22" fill="currentColor">
116
- <tspan font-style="italic">f</tspan><tspan>(</tspan><tspan font-style="italic">x</tspan><tspan>) = </tspan>
117
- </text>
118
- <text x="90" y="18" font-family="Georgia,serif" font-size="15" fill="currentColor">&#x221E;</text>
119
- <text x="88" y="36" font-family="Georgia,serif" font-size="28" fill="currentColor">&#x2211;</text>
120
- <text x="83" y="56" font-family="Georgia,serif" font-size="14" fill="currentColor"><tspan font-style="italic">n</tspan>=0</text>
121
- <text x="126" y="26" font-family="Georgia,serif" font-size="17" fill="currentColor">
122
- <tspan font-style="italic">f</tspan><tspan font-size="12" baseline-shift="super">(<tspan font-style="italic">n</tspan>)</tspan>(<tspan font-style="italic">a</tspan>)
123
- </text>
124
- <line x1="124" y1="32" x2="178" y2="32" stroke="currentColor" stroke-width="1.2"/>
125
- <text x="138" y="52" font-family="Georgia,serif" font-size="17" fill="currentColor">
126
- <tspan font-style="italic">n</tspan><tspan>!</tspan>
127
- </text>
128
- <text x="184" y="38" font-family="Georgia,serif" font-size="22" fill="currentColor">
129
- (<tspan font-style="italic">x&#x2212;a</tspan>)<tspan font-size="14" baseline-shift="super"><tspan font-style="italic">n</tspan></tspan>
130
- </text>
131
- </svg>`
132
- },
133
- {
134
- label: "navier-stokes",
135
- w: 370,
136
- h: 60,
137
- svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 370 60" width="370" height="60">
138
- <text x="10" y="40" font-family="Georgia,serif" font-size="22" fill="currentColor">
139
- &#x03C1;(&#x2202;<tspan font-style="italic">t</tspan><tspan font-weight="bold" font-style="italic">u</tspan> + <tspan font-style="italic">u</tspan>&#x22C5;&#x2207;<tspan font-style="italic">u</tspan>) = &#x2212;&#x2207;<tspan font-style="italic">p</tspan> + &#x03BC;&#x2207;<tspan font-size="16" baseline-shift="super">2</tspan><tspan font-weight="bold" font-style="italic">u</tspan>
140
- </text>
141
- </svg>`
142
- },
143
- {
144
- label: "maxwell",
145
- w: 210,
146
- h: 70,
147
- svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210 70" width="210" height="70">
148
- <text x="10" y="44" font-family="Georgia,serif" font-size="30" fill="currentColor">
149
- &#x2207; &#x22C5; <tspan font-weight="bold" font-style="italic">E</tspan> =
150
- </text>
151
- <text x="136" y="32" font-family="Georgia,serif" font-size="22" fill="currentColor">&#x03C1;</text>
152
- <line x1="134" y1="38" x2="165" y2="38" stroke="currentColor" stroke-width="1.4"/>
153
- <text x="136" y="58" font-family="Georgia,serif" font-size="22" fill="currentColor">&#x03B5;<tspan font-size="14" baseline-shift="sub">0</tspan></text>
154
- </svg>`
155
- },
156
- {
157
- label: "riemann",
158
- w: 200,
159
- h: 80,
160
- svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80" width="200" height="80">
161
- <text x="10" y="38" font-family="Georgia,serif" font-size="22" fill="currentColor">
162
- &#x03B6;(<tspan font-style="italic">s</tspan>) =
163
- </text>
164
- <text x="85" y="18" font-family="Georgia,serif" font-size="15" fill="currentColor">&#x221E;</text>
165
- <text x="82" y="36" font-family="Georgia,serif" font-size="28" fill="currentColor">&#x2211;</text>
166
- <text x="78" y="56" font-family="Georgia,serif" font-size="14" fill="currentColor"><tspan font-style="italic">n</tspan>=1</text>
167
- <text x="120" y="26" font-family="Georgia,serif" font-size="18" fill="currentColor">1</text>
168
- <line x1="118" y1="32" x2="152" y2="32" stroke="currentColor" stroke-width="1.2"/>
169
- <text x="120" y="52" font-family="Georgia,serif" font-size="18" fill="currentColor">
170
- <tspan font-style="italic">n</tspan><tspan font-size="12" baseline-shift="super"><tspan font-style="italic">s</tspan></tspan>
171
- </text>
172
- </svg>`
173
- },
174
- {
175
- label: "bayes",
176
- w: 300,
177
- h: 80,
178
- svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 80" width="300" height="80">
179
- <text x="10" y="36" font-family="Georgia,serif" font-size="22" fill="currentColor">
180
- <tspan font-style="italic">P</tspan>(<tspan font-style="italic">A</tspan>|<tspan font-style="italic">B</tspan>) =
181
- </text>
182
- <text x="118" y="24" font-family="Georgia,serif" font-size="20" fill="currentColor">
183
- <tspan font-style="italic">P</tspan>(<tspan font-style="italic">B</tspan>|<tspan font-style="italic">A</tspan>)<tspan font-style="italic">P</tspan>(<tspan font-style="italic">A</tspan>)
184
- </text>
185
- <line x1="116" y1="32" x2="258" y2="32" stroke="currentColor" stroke-width="1.3"/>
186
- <text x="162" y="56" font-family="Georgia,serif" font-size="20" fill="currentColor">
187
- <tspan font-style="italic">P</tspan>(<tspan font-style="italic">B</tspan>)
188
- </text>
189
- </svg>`
190
- }
191
- ];
192
- var DEFAULT_COLORS = [
193
- "#313695",
194
- "#4575b4",
195
- "#74add1",
196
- "#abd9e9",
197
- "#fee090",
198
- "#fdae61",
199
- "#f46d43",
200
- "#d73027",
201
- "#a50026"
202
- ];
203
- function rand(a, b) {
204
- return a + Math.random() * (b - a);
205
- }
206
- function easeIn(t) {
207
- return t * t * t;
208
- }
209
- function fadeEnvelope(t, fi = 0.18, fo = 0.78) {
210
- if (t < fi) return t / fi;
211
- if (t < fo) return 1;
212
- return 1 - (t - fo) / (1 - fo);
213
- }
214
- function applyEquation(el, eq, color) {
215
- el.innerHTML = eq.svg;
216
- const svg = el.querySelector("svg");
217
- if (svg) svg.style.color = color;
218
- el.style.width = `${eq.w}px`;
219
- el.style.height = `${eq.h}px`;
220
- el.style.filter = `drop-shadow(0 0 7px ${color}aa)`;
221
- }
222
- function createParticle(el, w, h, staggerT, colors) {
223
- const eq = EQUATIONS[Math.floor(Math.random() * EQUATIONS.length)];
224
- const color = colors[Math.floor(Math.random() * colors.length)];
225
- applyEquation(el, eq, color);
226
- return {
227
- el,
228
- eq,
229
- color,
230
- vx: w * rand(0.35, 0.65),
231
- vy: h * rand(0.35, 0.65),
232
- angle: rand(0, Math.PI * 2),
233
- maxDist: rand(Math.min(w, h) * 0.25, Math.min(w, h) * 0.7),
234
- scaleStart: rand(0.04, 0.1),
235
- scaleEnd: rand(1.4, 2.8),
236
- peakOpacity: rand(0.5, 0.85),
237
- duration: rand(5e3, 11e3),
238
- startTime: null,
239
- t: staggerT
240
- };
241
- }
242
- function resetParticle(p, w, h, colors) {
243
- const eq = EQUATIONS[Math.floor(Math.random() * EQUATIONS.length)];
244
- const color = colors[Math.floor(Math.random() * colors.length)];
245
- p.eq = eq;
246
- p.color = color;
247
- p.vx = w * rand(0.35, 0.65);
248
- p.vy = h * rand(0.35, 0.65);
249
- p.angle = rand(0, Math.PI * 2);
250
- p.maxDist = rand(Math.min(w, h) * 0.25, Math.min(w, h) * 0.7);
251
- p.scaleStart = rand(0.04, 0.1);
252
- p.scaleEnd = rand(1.4, 2.8);
253
- p.peakOpacity = rand(0.5, 0.85);
254
- p.duration = rand(5e3, 11e3);
255
- p.startTime = null;
256
- p.t = 0;
257
- applyEquation(p.el, eq, color);
258
- }
259
- function updateParticle(p, now, w, h, reverse, colors) {
260
- if (p.startTime === null) {
261
- p.startTime = now - p.t * p.duration;
262
- }
263
- let t = (now - p.startTime) / p.duration;
264
- if (t >= 1) {
265
- resetParticle(p, w, h, colors);
266
- p.startTime = now;
267
- t = 0;
268
- }
269
- const tz = easeIn(reverse ? 1 - t : t);
270
- const scale = p.scaleStart + (p.scaleEnd - p.scaleStart) * tz;
271
- const dist = p.maxDist * tz;
272
- const cx = p.vx + Math.cos(p.angle) * dist;
273
- const cy = p.vy + Math.sin(p.angle) * dist;
274
- p.el.style.left = `${cx - p.eq.w / 2}px`;
275
- p.el.style.top = `${cy - p.eq.h / 2}px`;
276
- p.el.style.opacity = String(p.peakOpacity * fadeEnvelope(t, 0.12, 0.75));
277
- p.el.style.transform = `scale(${scale})`;
278
- const blur = t < 0.15 ? (0.15 - t) / 0.15 * 5 : t > 0.8 ? (t - 0.8) / 0.2 * 3 : 0;
279
- p.el.style.filter = `drop-shadow(0 0 ${(6 * scale).toFixed(1)}px ${p.color}99) blur(${blur.toFixed(2)}px)`;
9
+ async function renderWhiteBitmaps() {
10
+ return Promise.all(
11
+ EQUATIONS.map(({ w, h, svg }) => {
12
+ const whiteSvg = svg.replace(/currentColor/g, "white");
13
+ const blob = new Blob([whiteSvg], { type: "image/svg+xml" });
14
+ const url = URL.createObjectURL(blob);
15
+ return new Promise((resolve, reject) => {
16
+ const img = new Image(w, h);
17
+ img.onload = () => {
18
+ const offscreen = new OffscreenCanvas(w, h);
19
+ const offCtx = offscreen.getContext("2d");
20
+ offCtx.drawImage(img, 0, 0, w, h);
21
+ resolve(offscreen.transferToImageBitmap());
22
+ URL.revokeObjectURL(url);
23
+ };
24
+ img.onerror = reject;
25
+ img.src = url;
26
+ });
27
+ })
28
+ );
280
29
  }
281
30
  var MathParticlesOverlay = ({
282
31
  backgroundColor = "#07070f",
32
+ blur = true,
283
33
  colors = DEFAULT_COLORS,
284
34
  density = 1,
285
35
  reverse = false,
286
- transitionDuration = 800
36
+ speed = 1,
37
+ fadeIn = true,
38
+ fadeInDuration = 800
287
39
  }) => {
288
- const containerRef = useRef(null);
40
+ const canvasRef = useRef(null);
41
+ const workerRef = useRef(null);
42
+ const blurRef = useRef(blur);
43
+ blurRef.current = blur;
289
44
  const colorsRef = useRef(colors);
290
45
  colorsRef.current = colors;
291
46
  const reverseRef = useRef(reverse);
292
47
  reverseRef.current = reverse;
48
+ const speedRef = useRef(speed);
49
+ speedRef.current = speed;
293
50
  useEffect(() => {
294
- const container = containerRef.current;
295
- if (!container) return;
296
- container.style.transition = `opacity ${transitionDuration}ms ease`;
297
- container.style.opacity = "0";
298
- void container.offsetHeight;
299
- container.style.opacity = "1";
300
- let w = container.clientWidth;
301
- let h = container.clientHeight;
302
- const count = Math.max(1, Math.floor(w * h / 55e3 * density));
303
- const particles = [];
304
- for (let i = 0; i < count; i++) {
305
- const el = document.createElement("div");
306
- el.style.cssText = "position:absolute;pointer-events:none;will-change:transform,opacity,filter;transform-origin:center center;opacity:0;";
307
- container.appendChild(el);
308
- particles.push(
309
- createParticle(el, w, h, Math.random(), colorsRef.current)
51
+ const canvas = canvasRef.current;
52
+ if (!canvas) return;
53
+ const w = canvas.clientWidth;
54
+ const h = canvas.clientHeight;
55
+ canvas.width = w;
56
+ canvas.height = h;
57
+ let stopped = false;
58
+ renderWhiteBitmaps().then((bitmaps) => {
59
+ if (stopped) {
60
+ for (const b of bitmaps) b.close();
61
+ return;
62
+ }
63
+ const offscreen = canvas.transferControlToOffscreen();
64
+ const worker = new Worker(new URL("./worker-entry.js", import.meta.url), {
65
+ type: "module"
66
+ });
67
+ workerRef.current = worker;
68
+ worker.postMessage(
69
+ {
70
+ type: "init",
71
+ canvas: offscreen,
72
+ bitmaps,
73
+ config: {
74
+ width: w,
75
+ height: h,
76
+ density,
77
+ blur: blurRef.current,
78
+ colors: colorsRef.current,
79
+ reverse: reverseRef.current,
80
+ speed: speedRef.current
81
+ }
82
+ },
83
+ [offscreen, ...bitmaps]
310
84
  );
85
+ });
86
+ if (fadeIn) {
87
+ canvas.style.transition = `opacity ${fadeInDuration}ms ease`;
88
+ canvas.style.opacity = "0";
89
+ void canvas.offsetHeight;
90
+ canvas.style.opacity = "1";
311
91
  }
312
- let rafId;
313
- const loop = (now) => {
314
- for (const p of particles)
315
- updateParticle(p, now, w, h, reverseRef.current, colorsRef.current);
316
- rafId = requestAnimationFrame(loop);
317
- };
318
- rafId = requestAnimationFrame(loop);
319
92
  const onResize = () => {
320
- w = container.clientWidth;
321
- h = container.clientHeight;
93
+ workerRef.current?.postMessage({
94
+ type: "resize",
95
+ width: canvas.clientWidth,
96
+ height: canvas.clientHeight
97
+ });
322
98
  };
323
99
  window.addEventListener("resize", onResize);
324
100
  return () => {
325
- cancelAnimationFrame(rafId);
101
+ stopped = true;
102
+ workerRef.current?.postMessage({ type: "stop" });
103
+ workerRef.current?.terminate();
104
+ workerRef.current = null;
326
105
  window.removeEventListener("resize", onResize);
327
- container.innerHTML = "";
328
106
  };
329
107
  }, []);
108
+ useEffect(() => {
109
+ workerRef.current?.postMessage({
110
+ type: "config",
111
+ config: { blur, colors, reverse, speed }
112
+ });
113
+ }, [blur, colors, reverse, speed]);
330
114
  return /* @__PURE__ */ jsx(
331
115
  "div",
332
116
  {
333
- ref: containerRef,
117
+ inert: true,
334
118
  style: {
335
- position: "absolute",
336
- inset: 0,
119
+ backgroundColor,
337
120
  overflow: "hidden",
338
- backgroundColor
339
- }
121
+ position: "absolute",
122
+ inset: 0
123
+ },
124
+ children: /* @__PURE__ */ jsx(
125
+ "canvas",
126
+ {
127
+ ref: canvasRef,
128
+ style: {
129
+ position: "absolute",
130
+ inset: 0,
131
+ height: "100%",
132
+ width: "100%"
133
+ }
134
+ }
135
+ )
340
136
  }
341
137
  );
342
138
  };
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,140 @@
1
+ import {
2
+ EQUATIONS
3
+ } from "./chunk-7SB7KDRV.js";
4
+
5
+ // src/worker-entry.ts
6
+ var canvas;
7
+ var ctx;
8
+ var whiteBitmaps;
9
+ var particles = [];
10
+ var config;
11
+ var loopId;
12
+ function rand(a, b) {
13
+ return a + Math.random() * (b - a);
14
+ }
15
+ function easeIn(t) {
16
+ return t * t * t;
17
+ }
18
+ function fadeEnvelope(t, fi = 0.18, fo = 0.78) {
19
+ if (t < fi) return t / fi;
20
+ if (t < fo) return 1;
21
+ return 1 - (t - fo) / (1 - fo);
22
+ }
23
+ function tintBitmap(white, color, w, h) {
24
+ const temp = new OffscreenCanvas(w, h);
25
+ const tempCtx = temp.getContext("2d");
26
+ tempCtx.drawImage(white, 0, 0, w, h);
27
+ tempCtx.globalCompositeOperation = "source-atop";
28
+ tempCtx.fillStyle = color;
29
+ tempCtx.fillRect(0, 0, w, h);
30
+ return temp.transferToImageBitmap();
31
+ }
32
+ function createParticle(w, h, staggerT, colors, speed) {
33
+ const eqIndex = Math.floor(Math.random() * EQUATIONS.length);
34
+ const eq = EQUATIONS[eqIndex];
35
+ const color = colors[Math.floor(Math.random() * colors.length)];
36
+ return {
37
+ bitmap: tintBitmap(whiteBitmaps[eqIndex], color, eq.w, eq.h),
38
+ eqIndex,
39
+ color,
40
+ vx: w * rand(0.35, 0.65),
41
+ vy: h * rand(0.35, 0.65),
42
+ angle: rand(0, Math.PI * 2),
43
+ maxDist: rand(Math.min(w, h) * 0.25, Math.min(w, h) * 0.7),
44
+ scaleStart: rand(0.04, 0.1),
45
+ scaleEnd: rand(1.4, 2.8),
46
+ peakOpacity: rand(0.5, 0.85),
47
+ duration: rand(5e3, 11e3) / speed,
48
+ startTime: null,
49
+ t: staggerT
50
+ };
51
+ }
52
+ function resetParticle(p, w, h, colors, speed) {
53
+ p.bitmap.close();
54
+ p.eqIndex = Math.floor(Math.random() * EQUATIONS.length);
55
+ const eq = EQUATIONS[p.eqIndex];
56
+ p.color = colors[Math.floor(Math.random() * colors.length)];
57
+ p.bitmap = tintBitmap(whiteBitmaps[p.eqIndex], p.color, eq.w, eq.h);
58
+ p.vx = w * rand(0.35, 0.65);
59
+ p.vy = h * rand(0.35, 0.65);
60
+ p.angle = rand(0, Math.PI * 2);
61
+ p.maxDist = rand(Math.min(w, h) * 0.25, Math.min(w, h) * 0.7);
62
+ p.scaleStart = rand(0.04, 0.1);
63
+ p.scaleEnd = rand(1.4, 2.8);
64
+ p.peakOpacity = rand(0.5, 0.85);
65
+ p.duration = rand(5e3, 11e3) / speed;
66
+ p.startTime = null;
67
+ p.t = 0;
68
+ }
69
+ function drawParticle(p, now) {
70
+ const eq = EQUATIONS[p.eqIndex];
71
+ const w = canvas.width;
72
+ const h = canvas.height;
73
+ if (p.startTime === null) {
74
+ p.startTime = now - p.t * p.duration;
75
+ }
76
+ let t = (now - p.startTime) / p.duration;
77
+ if (t >= 1) {
78
+ resetParticle(p, w, h, config.colors, config.speed);
79
+ p.startTime = now;
80
+ t = 0;
81
+ }
82
+ const tz = easeIn(config.reverse ? 1 - t : t);
83
+ const scale = p.scaleStart + (p.scaleEnd - p.scaleStart) * tz;
84
+ const dist = p.maxDist * tz;
85
+ const cx = p.vx + Math.cos(p.angle) * dist;
86
+ const cy = p.vy + Math.sin(p.angle) * dist;
87
+ const opacity = p.peakOpacity * fadeEnvelope(t, 0.12, 0.75);
88
+ ctx.save();
89
+ ctx.globalAlpha = opacity;
90
+ ctx.shadowColor = p.color + "99";
91
+ ctx.shadowBlur = 6 * scale;
92
+ if (config.blur) {
93
+ const blurAmount = t < 0.15 ? (0.15 - t) / 0.15 * 5 : t > 0.8 ? (t - 0.8) / 0.2 * 3 : 0;
94
+ if (blurAmount > 0) ctx.filter = `blur(${blurAmount.toFixed(1)}px)`;
95
+ }
96
+ ctx.translate(cx, cy);
97
+ ctx.scale(scale, scale);
98
+ ctx.drawImage(p.bitmap, -eq.w / 2, -eq.h / 2, eq.w, eq.h);
99
+ ctx.restore();
100
+ }
101
+ function initParticles() {
102
+ for (const p of particles) p.bitmap.close();
103
+ particles = [];
104
+ const { width, height, density, colors, speed } = config;
105
+ const count = Math.max(1, Math.floor(width * height / 55e3 * density));
106
+ for (let i = 0; i < count; i++) {
107
+ particles.push(createParticle(width, height, Math.random(), colors, speed));
108
+ }
109
+ }
110
+ function loop() {
111
+ const now = performance.now();
112
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
113
+ for (const p of particles) drawParticle(p, now);
114
+ loopId = setTimeout(loop, 0);
115
+ }
116
+ self.onmessage = (e) => {
117
+ const { type } = e.data;
118
+ if (type === "init") {
119
+ canvas = e.data.canvas;
120
+ ctx = canvas.getContext("2d");
121
+ whiteBitmaps = e.data.bitmaps;
122
+ config = e.data.config;
123
+ canvas.width = config.width;
124
+ canvas.height = config.height;
125
+ initParticles();
126
+ loop();
127
+ } else if (type === "config") {
128
+ config = { ...config, ...e.data.config };
129
+ } else if (type === "resize") {
130
+ canvas.width = e.data.width;
131
+ canvas.height = e.data.height;
132
+ config.width = canvas.width;
133
+ config.height = canvas.height;
134
+ initParticles();
135
+ } else if (type === "stop") {
136
+ clearTimeout(loopId);
137
+ for (const p of particles) p.bitmap.close();
138
+ particles = [];
139
+ }
140
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slithy/math-particles",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Animated math equations particle overlay for React.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -30,8 +30,8 @@
30
30
  "tsup": "^8",
31
31
  "typescript": "^5",
32
32
  "vitest": "^4.1.2",
33
- "@slithy/tsconfig": "0.0.0",
34
- "@slithy/eslint-config": "0.0.0"
33
+ "@slithy/eslint-config": "0.0.0",
34
+ "@slithy/tsconfig": "0.0.0"
35
35
  },
36
36
  "author": {
37
37
  "name": "Matthew Campagna",
@@ -56,8 +56,8 @@
56
56
  },
57
57
  "scripts": {
58
58
  "clean": "rm -rf dist",
59
- "build": "rm -rf dist && tsup src/index.ts --format esm --dts",
60
- "dev": "tsup src/index.ts --format esm --watch",
59
+ "build": "rm -rf dist && tsup src/index.ts src/worker-entry.ts --format esm --dts",
60
+ "dev": "tsup src/index.ts src/worker-entry.ts --format esm --watch",
61
61
  "typecheck": "tsc --noEmit",
62
62
  "lint": "eslint .",
63
63
  "test": "vitest run",