@tsparticles/template-login 4.1.3 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +16 -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/package.json +1 -1
  61. package/template/vanilla/src/main.ts +1 -1
  62. package/template/vue2/package.json +24 -0
  63. package/template/vue2/src/App.vue +287 -0
  64. package/template/vue3/package.json +19 -0
  65. package/template/vue3/src/App.vue +286 -0
  66. package/template/vue3/src/main.ts +14 -0
  67. package/template/webcomponents/package.json +21 -0
  68. package/template/webcomponents/src/main.ts +125 -0
  69. package/template/webcomponents/src/style.css +151 -0
@@ -0,0 +1,139 @@
1
+ import { component$, useVisibleTask$, useSignal, $ } from "@builder.io/qwik";
2
+ import { Particles, initParticlesEngine } from "@tsparticles/qwik";
3
+ import { loadSlim } from "@tsparticles/slim";
4
+ import type { ISourceOptions } from "@tsparticles/engine";
5
+
6
+ function validateEmail(val: string): boolean {
7
+ return /^[^\s@]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/.test(val);
8
+ }
9
+
10
+ const options: ISourceOptions = {
11
+ background: { color: { value: "transparent" } },
12
+ fpsLimit: 60,
13
+ particles: {
14
+ number: { value: 60, density: { enable: true } },
15
+ color: { value: ["#6c5ce7", "#a29bfe", "#fd79a8", "#00cec9"] },
16
+ shape: { type: "circle" },
17
+ opacity: { value: 0.5, random: true },
18
+ size: { value: { min: 1, max: 3 }, random: true },
19
+ move: { enable: true, speed: 1, direction: "none", random: true, straight: false, outModes: { default: "out" } },
20
+ },
21
+ interactivity: {
22
+ events: { onHover: { enable: true, mode: "bubble" } },
23
+ modes: { bubble: { distance: 200, size: 6, duration: 2, opacity: 0.8 } },
24
+ },
25
+ detectRetina: true,
26
+ };
27
+
28
+ export default component$(() => {
29
+ const isLogin = useSignal(true);
30
+ const theme = useSignal("dark");
31
+ const email = useSignal("");
32
+ const password = useSignal("");
33
+ const confirm = useSignal("");
34
+ const emailErr = useSignal("");
35
+ const passErr = useSignal("");
36
+ const confirmErr = useSignal("");
37
+
38
+ // eslint-disable-next-line qwik/no-use-visible-task
39
+ useVisibleTask$(() => {
40
+ void initParticlesEngine(async (engine) => {
41
+ await loadSlim(engine);
42
+ });
43
+ theme.value = localStorage.getItem("theme") || "dark";
44
+ document.documentElement.setAttribute("data-theme", theme.value);
45
+ });
46
+
47
+ const toggleTheme = $(() => {
48
+ theme.value = theme.value === "dark" ? "light" : "dark";
49
+ document.documentElement.setAttribute("data-theme", theme.value);
50
+ localStorage.setItem("theme", theme.value);
51
+ });
52
+
53
+ const handleSubmit = $((e: Event) => {
54
+ e.preventDefault();
55
+ let valid = true;
56
+
57
+ if (!email.value || !validateEmail(email.value)) {
58
+ emailErr.value = "Please enter a valid email address";
59
+ valid = false;
60
+ } else {
61
+ emailErr.value = "";
62
+ }
63
+
64
+ if (!password.value || password.value.length < 6) {
65
+ passErr.value = "Password must be at least 6 characters";
66
+ valid = false;
67
+ } else {
68
+ passErr.value = "";
69
+ }
70
+
71
+ if (!isLogin.value) {
72
+ if (password.value !== confirm.value) {
73
+ confirmErr.value = "Passwords do not match";
74
+ valid = false;
75
+ } else {
76
+ confirmErr.value = "";
77
+ }
78
+ }
79
+
80
+ if (!valid) return;
81
+
82
+ if (isLogin.value) {
83
+ alert("Logged in successfully! (demo)");
84
+ } else {
85
+ alert("Account created successfully! (demo)");
86
+ isLogin.value = true;
87
+ }
88
+
89
+ email.value = "";
90
+ password.value = "";
91
+ confirm.value = "";
92
+ });
93
+
94
+ const toggleMode = $((e: Event) => {
95
+ e.preventDefault();
96
+ isLogin.value = !isLogin.value;
97
+ emailErr.value = "";
98
+ passErr.value = "";
99
+ confirmErr.value = "";
100
+ });
101
+
102
+ return (
103
+ <div>
104
+ <div class="auth-container">
105
+ <div class="auth-card">
106
+ <div class="auth-header">
107
+ <h1>{isLogin.value ? "Login" : "Register"}</h1>
108
+ <button class="theme-btn" onClick$={toggleTheme} aria-label="Toggle theme">{theme.value === "dark" ? "🌙" : "☀️"}</button>
109
+ </div>
110
+ <form onSubmit$={handleSubmit} novalidate>
111
+ <div class="form-group">
112
+ <label for="email">Email</label>
113
+ <input id="email" type="email" placeholder="you@example.com" value={email.value} onInput$={(e: InputEvent) => { email.value = (e.target as HTMLInputElement).value; }} required />
114
+ <span class="error">{emailErr.value}</span>
115
+ </div>
116
+ <div class="form-group">
117
+ <label for="password">Password</label>
118
+ <input id="password" type="password" placeholder="Enter password" value={password.value} onInput$={(e: InputEvent) => { password.value = (e.target as HTMLInputElement).value; }} required />
119
+ <span class="error">{passErr.value}</span>
120
+ </div>
121
+ {!isLogin.value && (
122
+ <div class="form-group">
123
+ <label for="confirmPassword">Confirm Password</label>
124
+ <input id="confirmPassword" type="password" placeholder="Confirm password" value={confirm.value} onInput$={(e: InputEvent) => { confirm.value = (e.target as HTMLInputElement).value; }} />
125
+ <span class="error">{confirmErr.value}</span>
126
+ </div>
127
+ )}
128
+ <button type="submit">{isLogin.value ? "Sign In" : "Create Account"}</button>
129
+ </form>
130
+ <p class="toggle-text">
131
+ <span>{isLogin.value ? "Don't have an account?" : "Already have an account?"}</span>
132
+ <a href="#" onClick$={toggleMode}>{isLogin.value ? "Register" : "Login"}</a>
133
+ </p>
134
+ </div>
135
+ </div>
136
+ <Particles id="tsparticles" options={options} />
137
+ </div>
138
+ );
139
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{packageName}}",
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
+ "react": "^19.2.5",
13
+ "react-dom": "^19.2.5",
14
+ "@tsparticles/react": "^4.1.3"
15
+ },
16
+ "devDependencies": {
17
+ "@vitejs/plugin-react": "^6.0.1"
18
+ }
19
+ }
@@ -0,0 +1,151 @@
1
+ :root {
2
+ --bg: #0f0f1a;
3
+ --card-bg: rgba(255, 255, 255, 0.05);
4
+ --text: #fff;
5
+ --border: rgba(255, 255, 255, 0.1);
6
+ --input-bg: rgba(255, 255, 255, 0.08);
7
+ --accent: #6c5ce7;
8
+ --accent-hover: #a29bfe;
9
+ --error: #e74c3c;
10
+ }
11
+
12
+ [data-theme="light"] {
13
+ --bg: #f0f0f5;
14
+ --card-bg: rgba(255, 255, 255, 0.8);
15
+ --text: #1a1a2e;
16
+ --border: rgba(0, 0, 0, 0.1);
17
+ --input-bg: rgba(0, 0, 0, 0.05);
18
+ }
19
+
20
+ * {
21
+ box-sizing: border-box;
22
+ }
23
+
24
+ body {
25
+ margin: 0;
26
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
27
+ background: var(--bg);
28
+ color: var(--text);
29
+ transition: background 0.3s, color 0.3s;
30
+ }
31
+
32
+ .auth-container {
33
+ position: absolute;
34
+ top: 50%;
35
+ left: 50%;
36
+ transform: translate(-50%, -50%);
37
+ z-index: 10;
38
+ width: 100%;
39
+ max-width: 400px;
40
+ padding: 1rem;
41
+ }
42
+
43
+ .auth-card {
44
+ background: var(--card-bg);
45
+ backdrop-filter: blur(10px);
46
+ border: 1px solid var(--border);
47
+ border-radius: 16px;
48
+ padding: 2rem;
49
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
50
+ }
51
+
52
+ .auth-header {
53
+ display: flex;
54
+ justify-content: space-between;
55
+ align-items: center;
56
+ margin-bottom: 1.5rem;
57
+ }
58
+
59
+ .auth-header h1 {
60
+ margin: 0;
61
+ font-size: 1.8em;
62
+ }
63
+
64
+ .theme-btn {
65
+ background: none;
66
+ border: 1px solid var(--border);
67
+ border-radius: 8px;
68
+ padding: 0.4em 0.6em;
69
+ font-size: 1.2em;
70
+ cursor: pointer;
71
+ transition: background 0.2s;
72
+ }
73
+
74
+ .theme-btn:hover {
75
+ background: var(--input-bg);
76
+ }
77
+
78
+ .form-group {
79
+ margin-bottom: 1rem;
80
+ }
81
+
82
+ label {
83
+ display: block;
84
+ margin-bottom: 0.3em;
85
+ font-size: 0.9em;
86
+ opacity: 0.8;
87
+ }
88
+
89
+ input {
90
+ width: 100%;
91
+ padding: 0.7em 1em;
92
+ font-size: 1em;
93
+ border: 1px solid var(--border);
94
+ border-radius: 8px;
95
+ background: var(--input-bg);
96
+ color: var(--text);
97
+ outline: none;
98
+ transition: border-color 0.2s;
99
+ }
100
+
101
+ input:focus {
102
+ border-color: var(--accent);
103
+ }
104
+
105
+ input::placeholder {
106
+ color: var(--text);
107
+ opacity: 0.4;
108
+ }
109
+
110
+ .error {
111
+ display: block;
112
+ margin-top: 0.3em;
113
+ font-size: 0.8em;
114
+ color: var(--error);
115
+ min-height: 1em;
116
+ }
117
+
118
+ button[type="submit"] {
119
+ width: 100%;
120
+ padding: 0.8em;
121
+ font-size: 1em;
122
+ font-weight: 600;
123
+ border: none;
124
+ border-radius: 8px;
125
+ background: var(--accent);
126
+ color: #fff;
127
+ cursor: pointer;
128
+ transition: background 0.2s;
129
+ margin-top: 0.5rem;
130
+ }
131
+
132
+ button[type="submit"]:hover {
133
+ background: var(--accent-hover);
134
+ }
135
+
136
+ .toggle-text {
137
+ text-align: center;
138
+ margin-top: 1.5rem;
139
+ font-size: 0.9em;
140
+ opacity: 0.7;
141
+ }
142
+
143
+ .toggle-text a {
144
+ color: var(--accent);
145
+ text-decoration: none;
146
+ margin-left: 0.3em;
147
+ }
148
+
149
+ .toggle-text a:hover {
150
+ text-decoration: underline;
151
+ }
@@ -0,0 +1,140 @@
1
+ import { useState, useEffect } from "react";
2
+ import Particles, { ParticlesProvider } from "@tsparticles/react";
3
+ import { loadSlim } from "@tsparticles/slim";
4
+ import type { Engine, ISourceOptions } from "@tsparticles/engine";
5
+ import "./App.css";
6
+
7
+ async function init(engine: Engine): Promise<void> {
8
+ await loadSlim(engine);
9
+ }
10
+
11
+ const options: ISourceOptions = {
12
+ background: { color: { value: "transparent" } },
13
+ fpsLimit: 60,
14
+ particles: {
15
+ number: { value: 60, density: { enable: true } },
16
+ color: { value: ["#6c5ce7", "#a29bfe", "#fd79a8", "#00cec9"] },
17
+ shape: { type: "circle" },
18
+ opacity: { value: 0.5, random: true },
19
+ size: { value: { min: 1, max: 3 }, random: true },
20
+ move: { enable: true, speed: 1, direction: "none", random: true, straight: false, outModes: { default: "out" } },
21
+ },
22
+ interactivity: {
23
+ events: { onHover: { enable: true, mode: "bubble" } },
24
+ modes: { bubble: { distance: 200, size: 6, duration: 2, opacity: 0.8 } },
25
+ },
26
+ detectRetina: true,
27
+ };
28
+
29
+ function validateEmail(email: string): boolean {
30
+ return /^[^\s@]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/.test(email);
31
+ }
32
+
33
+ export default function App() {
34
+ const [theme, setTheme] = useState(() => localStorage.getItem("theme") || "dark");
35
+ const [loginMode, setLoginMode] = useState(true);
36
+ const [email, setEmail] = useState("");
37
+ const [password, setPassword] = useState("");
38
+ const [confirm, setConfirm] = useState("");
39
+ const [emailErr, setEmailErr] = useState("");
40
+ const [passErr, setPassErr] = useState("");
41
+ const [confirmErr, setConfirmErr] = useState("");
42
+
43
+ useEffect(() => {
44
+ document.documentElement.setAttribute("data-theme", theme);
45
+ localStorage.setItem("theme", theme);
46
+ }, [theme]);
47
+
48
+ function toggleTheme() {
49
+ setTheme((t) => (t === "dark" ? "light" : "dark"));
50
+ }
51
+
52
+ function handleSubmit(e: React.FormEvent) {
53
+ e.preventDefault();
54
+ let valid = true;
55
+
56
+ if (!email || !validateEmail(email)) {
57
+ setEmailErr("Please enter a valid email address");
58
+ valid = false;
59
+ } else {
60
+ setEmailErr("");
61
+ }
62
+
63
+ if (!password || password.length < 6) {
64
+ setPassErr("Password must be at least 6 characters");
65
+ valid = false;
66
+ } else {
67
+ setPassErr("");
68
+ }
69
+
70
+ if (!loginMode) {
71
+ if (password !== confirm) {
72
+ setConfirmErr("Passwords do not match");
73
+ valid = false;
74
+ } else {
75
+ setConfirmErr("");
76
+ }
77
+ }
78
+
79
+ if (!valid) return;
80
+
81
+ if (loginMode) {
82
+ alert("Logged in successfully! (demo)");
83
+ } else {
84
+ alert("Account created successfully! (demo)");
85
+ setLoginMode(true);
86
+ }
87
+
88
+ setEmail("");
89
+ setPassword("");
90
+ setConfirm("");
91
+ }
92
+
93
+ function toggleMode(e: React.MouseEvent) {
94
+ e.preventDefault();
95
+ setLoginMode((m) => !m);
96
+ setEmailErr("");
97
+ setPassErr("");
98
+ setConfirmErr("");
99
+ }
100
+
101
+ return (
102
+ <ParticlesProvider init={init}>
103
+ <Particles id="tsparticles" options={options} />
104
+ <div className="auth-container">
105
+ <div className="auth-card">
106
+ <div className="auth-header">
107
+ <h1>{loginMode ? "Login" : "Register"}</h1>
108
+ <button className="theme-btn" onClick={toggleTheme} aria-label="Toggle theme">
109
+ {theme === "dark" ? "\ud83c\udf19" : "\u2600\ufe0f"}
110
+ </button>
111
+ </div>
112
+ <form onSubmit={handleSubmit} noValidate>
113
+ <div className="form-group">
114
+ <label htmlFor="email">Email</label>
115
+ <input id="email" type="email" placeholder="you@example.com" value={email} onChange={(e) => setEmail(e.target.value)} required />
116
+ <span className="error">{emailErr}</span>
117
+ </div>
118
+ <div className="form-group">
119
+ <label htmlFor="password">Password</label>
120
+ <input id="password" type="password" placeholder="Enter password" value={password} onChange={(e) => setPassword(e.target.value)} required />
121
+ <span className="error">{passErr}</span>
122
+ </div>
123
+ {!loginMode && (
124
+ <div className="form-group">
125
+ <label htmlFor="confirmPassword">Confirm Password</label>
126
+ <input id="confirmPassword" type="password" placeholder="Confirm password" value={confirm} onChange={(e) => setConfirm(e.target.value)} />
127
+ <span className="error">{confirmErr}</span>
128
+ </div>
129
+ )}
130
+ <button type="submit">{loginMode ? "Sign In" : "Create Account"}</button>
131
+ </form>
132
+ <p className="toggle-text">
133
+ <span>{loginMode ? "Don't have an account?" : "Already have an account?"}</span>
134
+ <a href="#" onClick={toggleMode}>{loginMode ? "Register" : "Login"}</a>
135
+ </p>
136
+ </div>
137
+ </div>
138
+ </ParticlesProvider>
139
+ );
140
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "{{packageName}}",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "riot": "^10.0.0",
13
+ "@tsparticles/riot": "^4.1.3",
14
+ "@tsparticles/slim": "^4.1.3",
15
+ "@tsparticles/configs": "^4.1.3",
16
+ "@tsparticles/engine": "^4.1.3"
17
+ },
18
+ "devDependencies": {
19
+ "@riotjs/compiler": "^10.0.0",
20
+ "rollup-plugin-riot": "^10.0.0",
21
+ "typescript": "^5.7.2",
22
+ "vite": "^6.0.0"
23
+ }
24
+ }
@@ -0,0 +1,156 @@
1
+ <app>
2
+ <div class="auth-container">
3
+ <div class="auth-card">
4
+ <div class="auth-header">
5
+ <h1>{ state.isLogin ? 'Login' : 'Register' }</h1>
6
+ <button class="theme-btn" onclick={ toggleTheme } aria-label="Toggle theme">{ state.theme === 'dark' ? '🌙' : '☀️' }</button>
7
+ </div>
8
+ <form onsubmit={ handleSubmit } novalidate>
9
+ <div class="form-group">
10
+ <label for="email">Email</label>
11
+ <input id="email" type="email" placeholder="you@example.com" ref="emailInput" required />
12
+ <span class="error">{ state.emailErr }</span>
13
+ </div>
14
+ <div class="form-group">
15
+ <label for="password">Password</label>
16
+ <input id="password" type="password" placeholder="Enter password" ref="passwordInput" required />
17
+ <span class="error">{ state.passErr }</span>
18
+ </div>
19
+ <div if={ !state.isLogin } class="form-group">
20
+ <label for="confirmPassword">Confirm Password</label>
21
+ <input id="confirmPassword" type="password" placeholder="Confirm password" ref="confirmInput" />
22
+ <span class="error">{ state.confirmErr }</span>
23
+ </div>
24
+ <button type="submit">{ state.isLogin ? 'Sign In' : 'Create Account' }</button>
25
+ </form>
26
+ <p class="toggle-text">
27
+ <span>{ state.isLogin ? "Don't have an account?" : 'Already have an account?' }</span>
28
+ <a href="#" onclick={ toggleMode }>{ state.isLogin ? 'Register' : 'Login' }</a>
29
+ </p>
30
+ </div>
31
+ </div>
32
+ <riot-particles id="tsparticles" options={ state.options }></riot-particles>
33
+
34
+ <script>
35
+ import { initParticlesEngine } from "@tsparticles/riot";
36
+ import { loadSlim } from "@tsparticles/slim";
37
+
38
+ function validateEmail(val) {
39
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val);
40
+ }
41
+
42
+ export default {
43
+ state: {
44
+ isLogin: true,
45
+ theme: localStorage.getItem("theme") || "dark",
46
+ email: "",
47
+ password: "",
48
+ confirm: "",
49
+ emailErr: "",
50
+ passErr: "",
51
+ confirmErr: "",
52
+ options: {
53
+ background: { color: { value: "transparent" } },
54
+ fpsLimit: 60,
55
+ particles: {
56
+ number: { value: 60, density: { enable: true } },
57
+ color: { value: ["#6c5ce7", "#a29bfe", "#fd79a8", "#00cec9"] },
58
+ shape: { type: "circle" },
59
+ opacity: { value: 0.5, random: true },
60
+ size: { value: { min: 1, max: 3 }, random: true },
61
+ move: { enable: true, speed: 1, direction: "none", random: true, straight: false, outModes: { default: "out" } },
62
+ },
63
+ interactivity: {
64
+ events: { onHover: { enable: true, mode: "bubble" } },
65
+ modes: { bubble: { distance: 200, size: 6, duration: 2, opacity: 0.8 } },
66
+ },
67
+ detectRetina: true,
68
+ }
69
+ },
70
+ onMounted() {
71
+ document.documentElement.setAttribute("data-theme", this.state.theme);
72
+ void initParticlesEngine(async (engine) => {
73
+ await loadSlim(engine);
74
+ });
75
+ },
76
+ toggleTheme() {
77
+ const t = this.state.theme === "dark" ? "light" : "dark";
78
+ this.update({ theme: t });
79
+ document.documentElement.setAttribute("data-theme", t);
80
+ localStorage.setItem("theme", t);
81
+ },
82
+ handleSubmit(e) {
83
+ e.preventDefault();
84
+ let valid = true;
85
+
86
+ const email = this.refs.emailInput.value;
87
+ const password = this.refs.passwordInput.value;
88
+ const confirm = this.refs.confirmInput ? this.refs.confirmInput.value : "";
89
+
90
+ if (!email || !validateEmail(email)) {
91
+ this.update({ emailErr: "Please enter a valid email address" });
92
+ valid = false;
93
+ } else {
94
+ this.update({ emailErr: "" });
95
+ }
96
+
97
+ if (!password || password.length < 6) {
98
+ this.update({ passErr: "Password must be at least 6 characters" });
99
+ valid = false;
100
+ } else {
101
+ this.update({ passErr: "" });
102
+ }
103
+
104
+ if (!this.state.isLogin) {
105
+ if (password !== confirm) {
106
+ this.update({ confirmErr: "Passwords do not match" });
107
+ valid = false;
108
+ } else {
109
+ this.update({ confirmErr: "" });
110
+ }
111
+ }
112
+
113
+ if (!valid) return;
114
+
115
+ if (this.state.isLogin) {
116
+ alert("Logged in successfully! (demo)");
117
+ } else {
118
+ alert("Account created successfully! (demo)");
119
+ this.update({ isLogin: true });
120
+ }
121
+
122
+ this.refs.emailInput.value = "";
123
+ this.refs.passwordInput.value = "";
124
+ if (this.refs.confirmInput) this.refs.confirmInput.value = "";
125
+ },
126
+ toggleMode(e) {
127
+ e.preventDefault();
128
+ this.update({ isLogin: !this.state.isLogin, emailErr: "", passErr: "", confirmErr: "" });
129
+ }
130
+ }
131
+ </script>
132
+
133
+ <style>
134
+ body { margin: 0; font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; background: var(--bg); color: var(--text); transition: background 0.3s, color 0.3s; }
135
+ :root { --bg: #0f0f1a; --card-bg: rgba(255, 255, 255, 0.05); --text: #fff; --border: rgba(255, 255, 255, 0.1); --input-bg: rgba(255, 255, 255, 0.08); --accent: #6c5ce7; --accent-hover: #a29bfe; --error: #e74c3c; }
136
+ [data-theme="light"] { --bg: #f0f0f5; --card-bg: rgba(255, 255, 255, 0.8); --text: #1a1a2e; --border: rgba(0, 0, 0, 0.1); --input-bg: rgba(0, 0, 0, 0.05); }
137
+ * { box-sizing: border-box; }
138
+ .auth-container { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10; width: 100%; max-width: 400px; padding: 1rem; }
139
+ .auth-card { background: var(--card-bg); backdrop-filter: blur(10px); border: 1px solid var(--border); border-radius: 16px; padding: 2rem; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); }
140
+ .auth-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
141
+ .auth-header h1 { margin: 0; font-size: 1.8em; }
142
+ .theme-btn { background: none; border: 1px solid var(--border); border-radius: 8px; padding: 0.4em 0.6em; font-size: 1.2em; cursor: pointer; transition: background 0.2s; }
143
+ .theme-btn:hover { background: var(--input-bg); }
144
+ .form-group { margin-bottom: 1rem; }
145
+ label { display: block; margin-bottom: 0.3em; font-size: 0.9em; opacity: 0.8; }
146
+ input { width: 100%; padding: 0.7em 1em; font-size: 1em; border: 1px solid var(--border); border-radius: 8px; background: var(--input-bg); color: var(--text); outline: none; transition: border-color 0.2s; }
147
+ input:focus { border-color: var(--accent); }
148
+ input::placeholder { color: var(--text); opacity: 0.4; }
149
+ .error { display: block; margin-top: 0.3em; font-size: 0.8em; color: var(--error); min-height: 1em; }
150
+ button[type="submit"] { width: 100%; padding: 0.8em; font-size: 1em; font-weight: 600; border: none; border-radius: 8px; background: var(--accent); color: #fff; cursor: pointer; transition: background 0.2s; margin-top: 0.5rem; }
151
+ button[type="submit"]:hover { background: var(--accent-hover); }
152
+ .toggle-text { text-align: center; margin-top: 1.5rem; font-size: 0.9em; opacity: 0.7; }
153
+ .toggle-text a { color: var(--accent); text-decoration: none; margin-left: 0.3em; }
154
+ .toggle-text a:hover { text-decoration: underline; }
155
+ </style>
156
+ </app>
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "{{packageName}}",
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
+ "solid-js": "^1.8.11",
13
+ "@tsparticles/solid": "^4.1.3"
14
+ },
15
+ "devDependencies": {
16
+ "vite-plugin-solid": "^2.8.2"
17
+ }
18
+ }