@tsparticles/template-login 4.1.3 → 4.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.
Files changed (68) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +3 -0
  3. package/package.json +4 -3
  4. package/template/angular/package.json +34 -0
  5. package/template/angular/src/app/app.component.css +151 -0
  6. package/template/angular/src/app/app.component.html +32 -0
  7. package/template/angular/src/app/app.component.ts +105 -0
  8. package/template/angular/src/app/app.module.ts +13 -0
  9. package/template/angular-confetti/package.json +36 -0
  10. package/template/angular-confetti/src/app/app.component.css +151 -0
  11. package/template/angular-confetti/src/app/app.component.html +32 -0
  12. package/template/angular-confetti/src/app/app.component.ts +105 -0
  13. package/template/angular-confetti/src/app/app.module.ts +13 -0
  14. package/template/angular-fireworks/package.json +36 -0
  15. package/template/angular-fireworks/src/app/app.component.css +151 -0
  16. package/template/angular-fireworks/src/app/app.component.html +32 -0
  17. package/template/angular-fireworks/src/app/app.component.ts +105 -0
  18. package/template/angular-fireworks/src/app/app.module.ts +13 -0
  19. package/template/astro/package.json +22 -0
  20. package/template/astro/src/pages/index.astro +173 -0
  21. package/template/ember/app/templates/application.hbs +61 -0
  22. package/template/ember/package.json +42 -0
  23. package/template/inferno/package.json +24 -0
  24. package/template/inferno/src/App.css +151 -0
  25. package/template/inferno/src/App.tsx +158 -0
  26. package/template/jquery/package.json +22 -0
  27. package/template/jquery/src/main.ts +110 -0
  28. package/template/jquery/src/style.css +151 -0
  29. package/template/lit/package.json +22 -0
  30. package/template/lit/src/my-app.ts +143 -0
  31. package/template/nextjs/package.json +25 -0
  32. package/template/nextjs/src/app/page.tsx +168 -0
  33. package/template/nextjs/src/app/providers.tsx +13 -0
  34. package/template/nuxt2/package.json +17 -0
  35. package/template/nuxt2/pages/index.vue +303 -0
  36. package/template/nuxt3/app.vue +288 -0
  37. package/template/nuxt3/package.json +21 -0
  38. package/template/nuxt4/app.vue +288 -0
  39. package/template/nuxt4/package.json +21 -0
  40. package/template/preact/package.json +23 -0
  41. package/template/preact/src/App.css +151 -0
  42. package/template/preact/src/App.tsx +140 -0
  43. package/template/qwik/package.json +22 -0
  44. package/template/qwik/src/App.tsx +139 -0
  45. package/template/react/package.json +19 -0
  46. package/template/react/src/App.css +151 -0
  47. package/template/react/src/App.tsx +140 -0
  48. package/template/riot/package.json +24 -0
  49. package/template/riot/src/app.riot +156 -0
  50. package/template/solid/package.json +18 -0
  51. package/template/solid/src/App.tsx +169 -0
  52. package/template/solid/src/index.css +151 -0
  53. package/template/solid/src/main.tsx +9 -0
  54. package/template/stencil/package.json +20 -0
  55. package/template/stencil/src/components/app-home/app-home.tsx +164 -0
  56. package/template/svelte/package.json +20 -0
  57. package/template/svelte/src/App.svelte +325 -0
  58. package/template/svelte/src/main.ts +12 -0
  59. package/template/vanilla/LICENSE +21 -0
  60. package/template/vanilla/src/main.ts +1 -1
  61. package/template/vue2/package.json +24 -0
  62. package/template/vue2/src/App.vue +287 -0
  63. package/template/vue3/package.json +19 -0
  64. package/template/vue3/src/App.vue +286 -0
  65. package/template/vue3/src/main.ts +14 -0
  66. package/template/webcomponents/package.json +21 -0
  67. package/template/webcomponents/src/main.ts +125 -0
  68. package/template/webcomponents/src/style.css +151 -0
@@ -0,0 +1,287 @@
1
+ <template>
2
+ <div>
3
+ <vue-particles id="tsparticles" :options="options" />
4
+ <div class="auth-container">
5
+ <div class="auth-card">
6
+ <div class="auth-header">
7
+ <h1>{{ isLogin ? 'Login' : 'Register' }}</h1>
8
+ <button class="theme-btn" @click="toggleTheme" aria-label="Toggle theme">{{ theme === 'dark' ? '🌙' : '☀️' }}</button>
9
+ </div>
10
+ <form @submit="handleSubmit" novalidate>
11
+ <div class="form-group">
12
+ <label for="email">Email</label>
13
+ <input id="email" v-model="email" type="email" placeholder="you@example.com" required />
14
+ <span class="error">{{ emailErr }}</span>
15
+ </div>
16
+ <div class="form-group">
17
+ <label for="password">Password</label>
18
+ <input id="password" v-model="password" type="password" placeholder="Enter password" required />
19
+ <span class="error">{{ passErr }}</span>
20
+ </div>
21
+ <div v-if="!isLogin" class="form-group">
22
+ <label for="confirmPassword">Confirm Password</label>
23
+ <input id="confirmPassword" v-model="confirm" type="password" placeholder="Confirm password" />
24
+ <span class="error">{{ confirmErr }}</span>
25
+ </div>
26
+ <button type="submit">{{ isLogin ? 'Sign In' : 'Create Account' }}</button>
27
+ </form>
28
+ <p class="toggle-text">
29
+ <span>{{ isLogin ? "Don't have an account?" : 'Already have an account?' }}</span>
30
+ <a href="#" @click="toggleMode">{{ isLogin ? 'Register' : 'Login' }}</a>
31
+ </p>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </template>
36
+
37
+ <script lang="ts">
38
+ import Vue from "vue";
39
+ import type { ISourceOptions } from "@tsparticles/engine";
40
+
41
+ function validateEmail(val: string): boolean {
42
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val);
43
+ }
44
+
45
+ export default Vue.extend({
46
+ data() {
47
+ return {
48
+ isLogin: true,
49
+ theme: localStorage.getItem("theme") || "dark",
50
+ email: "",
51
+ password: "",
52
+ confirm: "",
53
+ emailErr: "",
54
+ passErr: "",
55
+ confirmErr: "",
56
+ options: {
57
+ background: { color: { value: "transparent" } },
58
+ fpsLimit: 60,
59
+ particles: {
60
+ number: { value: 60, density: { enable: true } },
61
+ color: { value: ["#6c5ce7", "#a29bfe", "#fd79a8", "#00cec9"] },
62
+ shape: { type: "circle" },
63
+ opacity: { value: 0.5, random: true },
64
+ size: { value: { min: 1, max: 3 }, random: true },
65
+ move: { enable: true, speed: 1, direction: "none", random: true, straight: false, outModes: { default: "out" } },
66
+ },
67
+ interactivity: {
68
+ events: { onHover: { enable: true, mode: "bubble" } },
69
+ modes: { bubble: { distance: 200, size: 6, duration: 2, opacity: 0.8 } },
70
+ },
71
+ detectRetina: true,
72
+ } as ISourceOptions,
73
+ };
74
+ },
75
+ mounted() {
76
+ document.documentElement.setAttribute("data-theme", this.theme);
77
+ },
78
+ methods: {
79
+ toggleTheme(): void {
80
+ this.theme = this.theme === "dark" ? "light" : "dark";
81
+ document.documentElement.setAttribute("data-theme", this.theme);
82
+ localStorage.setItem("theme", this.theme);
83
+ },
84
+ handleSubmit(e: Event): void {
85
+ e.preventDefault();
86
+ let valid = true;
87
+
88
+ if (!this.email || !validateEmail(this.email)) {
89
+ this.emailErr = "Please enter a valid email address";
90
+ valid = false;
91
+ } else {
92
+ this.emailErr = "";
93
+ }
94
+
95
+ if (!this.password || this.password.length < 6) {
96
+ this.passErr = "Password must be at least 6 characters";
97
+ valid = false;
98
+ } else {
99
+ this.passErr = "";
100
+ }
101
+
102
+ if (!this.isLogin) {
103
+ if (this.password !== this.confirm) {
104
+ this.confirmErr = "Passwords do not match";
105
+ valid = false;
106
+ } else {
107
+ this.confirmErr = "";
108
+ }
109
+ }
110
+
111
+ if (!valid) return;
112
+
113
+ if (this.isLogin) {
114
+ alert("Logged in successfully! (demo)");
115
+ } else {
116
+ alert("Account created successfully! (demo)");
117
+ this.isLogin = true;
118
+ }
119
+
120
+ this.email = "";
121
+ this.password = "";
122
+ this.confirm = "";
123
+ },
124
+ toggleMode(e: Event): void {
125
+ e.preventDefault();
126
+ this.isLogin = !this.isLogin;
127
+ this.emailErr = "";
128
+ this.passErr = "";
129
+ this.confirmErr = "";
130
+ },
131
+ },
132
+ });
133
+ </script>
134
+
135
+ <style>
136
+ :root {
137
+ --bg: #0f0f1a;
138
+ --card-bg: rgba(255, 255, 255, 0.05);
139
+ --text: #fff;
140
+ --border: rgba(255, 255, 255, 0.1);
141
+ --input-bg: rgba(255, 255, 255, 0.08);
142
+ --accent: #6c5ce7;
143
+ --accent-hover: #a29bfe;
144
+ --error: #e74c3c;
145
+ }
146
+
147
+ [data-theme="light"] {
148
+ --bg: #f0f0f5;
149
+ --card-bg: rgba(255, 255, 255, 0.8);
150
+ --text: #1a1a2e;
151
+ --border: rgba(0, 0, 0, 0.1);
152
+ --input-bg: rgba(0, 0, 0, 0.05);
153
+ }
154
+
155
+ * {
156
+ box-sizing: border-box;
157
+ }
158
+
159
+ body {
160
+ margin: 0;
161
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
162
+ background: var(--bg);
163
+ color: var(--text);
164
+ transition: background 0.3s, color 0.3s;
165
+ }
166
+
167
+ .auth-container {
168
+ position: absolute;
169
+ top: 50%;
170
+ left: 50%;
171
+ transform: translate(-50%, -50%);
172
+ z-index: 10;
173
+ width: 100%;
174
+ max-width: 400px;
175
+ padding: 1rem;
176
+ }
177
+
178
+ .auth-card {
179
+ background: var(--card-bg);
180
+ backdrop-filter: blur(10px);
181
+ border: 1px solid var(--border);
182
+ border-radius: 16px;
183
+ padding: 2rem;
184
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
185
+ }
186
+
187
+ .auth-header {
188
+ display: flex;
189
+ justify-content: space-between;
190
+ align-items: center;
191
+ margin-bottom: 1.5rem;
192
+ }
193
+
194
+ .auth-header h1 {
195
+ margin: 0;
196
+ font-size: 1.8em;
197
+ }
198
+
199
+ .theme-btn {
200
+ background: none;
201
+ border: 1px solid var(--border);
202
+ border-radius: 8px;
203
+ padding: 0.4em 0.6em;
204
+ font-size: 1.2em;
205
+ cursor: pointer;
206
+ transition: background 0.2s;
207
+ }
208
+
209
+ .theme-btn:hover {
210
+ background: var(--input-bg);
211
+ }
212
+
213
+ .form-group {
214
+ margin-bottom: 1rem;
215
+ }
216
+
217
+ label {
218
+ display: block;
219
+ margin-bottom: 0.3em;
220
+ font-size: 0.9em;
221
+ opacity: 0.8;
222
+ }
223
+
224
+ input {
225
+ width: 100%;
226
+ padding: 0.7em 1em;
227
+ font-size: 1em;
228
+ border: 1px solid var(--border);
229
+ border-radius: 8px;
230
+ background: var(--input-bg);
231
+ color: var(--text);
232
+ outline: none;
233
+ transition: border-color 0.2s;
234
+ }
235
+
236
+ input:focus {
237
+ border-color: var(--accent);
238
+ }
239
+
240
+ input::placeholder {
241
+ color: var(--text);
242
+ opacity: 0.4;
243
+ }
244
+
245
+ .error {
246
+ display: block;
247
+ margin-top: 0.3em;
248
+ font-size: 0.8em;
249
+ color: var(--error);
250
+ min-height: 1em;
251
+ }
252
+
253
+ button[type="submit"] {
254
+ width: 100%;
255
+ padding: 0.8em;
256
+ font-size: 1em;
257
+ font-weight: 600;
258
+ border: none;
259
+ border-radius: 8px;
260
+ background: var(--accent);
261
+ color: #fff;
262
+ cursor: pointer;
263
+ transition: background 0.2s;
264
+ margin-top: 0.5rem;
265
+ }
266
+
267
+ button[type="submit"]:hover {
268
+ background: var(--accent-hover);
269
+ }
270
+
271
+ .toggle-text {
272
+ text-align: center;
273
+ margin-top: 1.5rem;
274
+ font-size: 0.9em;
275
+ opacity: 0.7;
276
+ }
277
+
278
+ .toggle-text a {
279
+ color: var(--accent);
280
+ text-decoration: none;
281
+ margin-left: 0.3em;
282
+ }
283
+
284
+ .toggle-text a:hover {
285
+ text-decoration: underline;
286
+ }
287
+ </style>
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vue-tsc --noEmit && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "vue": "^3.5.32",
13
+ "@tsparticles/vue3": "^4.1.3"
14
+ },
15
+ "devDependencies": {
16
+ "@vitejs/plugin-vue": "^6.0.5",
17
+ "vue-tsc": "^3.2.6"
18
+ }
19
+ }
@@ -0,0 +1,286 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted } from "vue";
3
+ import type { ISourceOptions } from "@tsparticles/engine";
4
+
5
+ const isLogin = ref(true);
6
+ const theme = ref(localStorage.getItem("theme") || "dark");
7
+ const email = ref("");
8
+ const password = ref("");
9
+ const confirm = ref("");
10
+ const emailErr = ref("");
11
+ const passErr = ref("");
12
+ const confirmErr = ref("");
13
+
14
+ const options: ISourceOptions = {
15
+ background: { color: { value: "transparent" } },
16
+ fpsLimit: 60,
17
+ particles: {
18
+ number: { value: 60, density: { enable: true } },
19
+ color: { value: ["#6c5ce7", "#a29bfe", "#fd79a8", "#00cec9"] },
20
+ shape: { type: "circle" },
21
+ opacity: { value: 0.5, random: true },
22
+ size: { value: { min: 1, max: 3 }, random: true },
23
+ move: { enable: true, speed: 1, direction: "none", random: true, straight: false, outModes: { default: "out" } },
24
+ },
25
+ interactivity: {
26
+ events: { onHover: { enable: true, mode: "bubble" } },
27
+ modes: { bubble: { distance: 200, size: 6, duration: 2, opacity: 0.8 } },
28
+ },
29
+ detectRetina: true,
30
+ };
31
+
32
+ onMounted(() => {
33
+ document.documentElement.setAttribute("data-theme", theme.value);
34
+ });
35
+
36
+ function setTheme(t: string) {
37
+ theme.value = t;
38
+ document.documentElement.setAttribute("data-theme", t);
39
+ localStorage.setItem("theme", t);
40
+ }
41
+
42
+ function toggleTheme() {
43
+ setTheme(theme.value === "dark" ? "light" : "dark");
44
+ }
45
+
46
+ function validateEmail(val: string): boolean {
47
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val);
48
+ }
49
+
50
+ function handleSubmit(e: Event) {
51
+ e.preventDefault();
52
+ let valid = true;
53
+
54
+ if (!email.value || !validateEmail(email.value)) {
55
+ emailErr.value = "Please enter a valid email address";
56
+ valid = false;
57
+ } else {
58
+ emailErr.value = "";
59
+ }
60
+
61
+ if (!password.value || password.value.length < 6) {
62
+ passErr.value = "Password must be at least 6 characters";
63
+ valid = false;
64
+ } else {
65
+ passErr.value = "";
66
+ }
67
+
68
+ if (!isLogin.value) {
69
+ if (password.value !== confirm.value) {
70
+ confirmErr.value = "Passwords do not match";
71
+ valid = false;
72
+ } else {
73
+ confirmErr.value = "";
74
+ }
75
+ }
76
+
77
+ if (!valid) return;
78
+
79
+ if (isLogin.value) {
80
+ alert("Logged in successfully! (demo)");
81
+ } else {
82
+ alert("Account created successfully! (demo)");
83
+ isLogin.value = true;
84
+ }
85
+
86
+ email.value = "";
87
+ password.value = "";
88
+ confirm.value = "";
89
+ }
90
+
91
+ function toggleMode(e: MouseEvent) {
92
+ e.preventDefault();
93
+ isLogin.value = !isLogin.value;
94
+ emailErr.value = "";
95
+ passErr.value = "";
96
+ confirmErr.value = "";
97
+ }
98
+ </script>
99
+
100
+ <template>
101
+ <vue-particles id="tsparticles" :options="options" />
102
+ <div class="auth-container">
103
+ <div class="auth-card">
104
+ <div class="auth-header">
105
+ <h1>{{ isLogin ? 'Login' : 'Register' }}</h1>
106
+ <button class="theme-btn" @click="toggleTheme" aria-label="Toggle theme">{{ theme === 'dark' ? '🌙' : '☀️' }}</button>
107
+ </div>
108
+ <form @submit="handleSubmit" novalidate>
109
+ <div class="form-group">
110
+ <label for="email">Email</label>
111
+ <input id="email" v-model="email" type="email" placeholder="you@example.com" required />
112
+ <span class="error">{{ emailErr }}</span>
113
+ </div>
114
+ <div class="form-group">
115
+ <label for="password">Password</label>
116
+ <input id="password" v-model="password" type="password" placeholder="Enter password" required />
117
+ <span class="error">{{ passErr }}</span>
118
+ </div>
119
+ <div v-if="!isLogin" class="form-group">
120
+ <label for="confirmPassword">Confirm Password</label>
121
+ <input id="confirmPassword" v-model="confirm" type="password" placeholder="Confirm password" />
122
+ <span class="error">{{ confirmErr }}</span>
123
+ </div>
124
+ <button type="submit">{{ isLogin ? 'Sign In' : 'Create Account' }}</button>
125
+ </form>
126
+ <p class="toggle-text">
127
+ <span>{{ isLogin ? "Don't have an account?" : 'Already have an account?' }}</span>
128
+ <a href="#" @click="toggleMode">{{ isLogin ? 'Register' : 'Login' }}</a>
129
+ </p>
130
+ </div>
131
+ </div>
132
+ </template>
133
+
134
+ <style>
135
+ :root {
136
+ --bg: #0f0f1a;
137
+ --card-bg: rgba(255, 255, 255, 0.05);
138
+ --text: #fff;
139
+ --border: rgba(255, 255, 255, 0.1);
140
+ --input-bg: rgba(255, 255, 255, 0.08);
141
+ --accent: #6c5ce7;
142
+ --accent-hover: #a29bfe;
143
+ --error: #e74c3c;
144
+ }
145
+
146
+ [data-theme="light"] {
147
+ --bg: #f0f0f5;
148
+ --card-bg: rgba(255, 255, 255, 0.8);
149
+ --text: #1a1a2e;
150
+ --border: rgba(0, 0, 0, 0.1);
151
+ --input-bg: rgba(0, 0, 0, 0.05);
152
+ }
153
+
154
+ * {
155
+ box-sizing: border-box;
156
+ }
157
+
158
+ body {
159
+ margin: 0;
160
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
161
+ background: var(--bg);
162
+ color: var(--text);
163
+ transition: background 0.3s, color 0.3s;
164
+ }
165
+
166
+ .auth-container {
167
+ position: absolute;
168
+ top: 50%;
169
+ left: 50%;
170
+ transform: translate(-50%, -50%);
171
+ z-index: 10;
172
+ width: 100%;
173
+ max-width: 400px;
174
+ padding: 1rem;
175
+ }
176
+
177
+ .auth-card {
178
+ background: var(--card-bg);
179
+ backdrop-filter: blur(10px);
180
+ border: 1px solid var(--border);
181
+ border-radius: 16px;
182
+ padding: 2rem;
183
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
184
+ }
185
+
186
+ .auth-header {
187
+ display: flex;
188
+ justify-content: space-between;
189
+ align-items: center;
190
+ margin-bottom: 1.5rem;
191
+ }
192
+
193
+ .auth-header h1 {
194
+ margin: 0;
195
+ font-size: 1.8em;
196
+ }
197
+
198
+ .theme-btn {
199
+ background: none;
200
+ border: 1px solid var(--border);
201
+ border-radius: 8px;
202
+ padding: 0.4em 0.6em;
203
+ font-size: 1.2em;
204
+ cursor: pointer;
205
+ transition: background 0.2s;
206
+ }
207
+
208
+ .theme-btn:hover {
209
+ background: var(--input-bg);
210
+ }
211
+
212
+ .form-group {
213
+ margin-bottom: 1rem;
214
+ }
215
+
216
+ label {
217
+ display: block;
218
+ margin-bottom: 0.3em;
219
+ font-size: 0.9em;
220
+ opacity: 0.8;
221
+ }
222
+
223
+ input {
224
+ width: 100%;
225
+ padding: 0.7em 1em;
226
+ font-size: 1em;
227
+ border: 1px solid var(--border);
228
+ border-radius: 8px;
229
+ background: var(--input-bg);
230
+ color: var(--text);
231
+ outline: none;
232
+ transition: border-color 0.2s;
233
+ }
234
+
235
+ input:focus {
236
+ border-color: var(--accent);
237
+ }
238
+
239
+ input::placeholder {
240
+ color: var(--text);
241
+ opacity: 0.4;
242
+ }
243
+
244
+ .error {
245
+ display: block;
246
+ margin-top: 0.3em;
247
+ font-size: 0.8em;
248
+ color: var(--error);
249
+ min-height: 1em;
250
+ }
251
+
252
+ button[type="submit"] {
253
+ width: 100%;
254
+ padding: 0.8em;
255
+ font-size: 1em;
256
+ font-weight: 600;
257
+ border: none;
258
+ border-radius: 8px;
259
+ background: var(--accent);
260
+ color: #fff;
261
+ cursor: pointer;
262
+ transition: background 0.2s;
263
+ margin-top: 0.5rem;
264
+ }
265
+
266
+ button[type="submit"]:hover {
267
+ background: var(--accent-hover);
268
+ }
269
+
270
+ .toggle-text {
271
+ text-align: center;
272
+ margin-top: 1.5rem;
273
+ font-size: 0.9em;
274
+ opacity: 0.7;
275
+ }
276
+
277
+ .toggle-text a {
278
+ color: var(--accent);
279
+ text-decoration: none;
280
+ margin-left: 0.3em;
281
+ }
282
+
283
+ .toggle-text a:hover {
284
+ text-decoration: underline;
285
+ }
286
+ </style>
@@ -0,0 +1,14 @@
1
+ import { createApp } from "vue";
2
+ import Particles from "@tsparticles/vue3";
3
+ import { loadSlim } from "@tsparticles/slim";
4
+ import App from "./App.vue";
5
+
6
+ const app = createApp(App);
7
+
8
+ app.use(Particles, {
9
+ init: async (engine) => {
10
+ await loadSlim(engine);
11
+ },
12
+ });
13
+
14
+ app.mount("#app");
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@tsparticles/webcomponents": "^4.1.3",
13
+ "@tsparticles/slim": "^4.1.3",
14
+ "@tsparticles/configs": "^4.1.3",
15
+ "@tsparticles/engine": "^4.1.3"
16
+ },
17
+ "devDependencies": {
18
+ "typescript": "^5.7.2",
19
+ "vite": "^6.0.0"
20
+ }
21
+ }