@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,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": "{{projectName}}",
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": "{{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
+ "solid-js": "^1.8.11",
13
+ "@tsparticles/solid": "^4.1.3"
14
+ },
15
+ "devDependencies": {
16
+ "vite-plugin-solid": "^2.8.2"
17
+ }
18
+ }
@@ -0,0 +1,169 @@
1
+ import { createSignal, createEffect, onMount } from "solid-js";
2
+ import Particles, { initParticlesEngine } from "@tsparticles/solid";
3
+ import { loadSlim } from "@tsparticles/slim";
4
+ import type { ISourceOptions } from "@tsparticles/engine";
5
+
6
+ export default function App() {
7
+ const [isLogin, setIsLogin] = createSignal(true);
8
+ const [theme, setTheme] = createSignal(localStorage.getItem("theme") || "dark");
9
+ const [email, setEmail] = createSignal("");
10
+ const [password, setPassword] = createSignal("");
11
+ const [confirm, setConfirm] = createSignal("");
12
+ const [emailError, setEmailError] = createSignal("");
13
+ const [passwordError, setPasswordError] = createSignal("");
14
+ const [confirmError, setConfirmError] = createSignal("");
15
+
16
+ createEffect(() => {
17
+ document.documentElement.setAttribute("data-theme", theme());
18
+ });
19
+
20
+ onMount(() => {
21
+ void initParticlesEngine(async (engine) => {
22
+ await loadSlim(engine);
23
+ });
24
+ });
25
+
26
+ const options: ISourceOptions = {
27
+ background: { color: { value: "transparent" } },
28
+ fpsLimit: 60,
29
+ particles: {
30
+ number: { value: 60, density: { enable: true } },
31
+ color: { value: ["#6c5ce7", "#a29bfe", "#fd79a8", "#00cec9"] },
32
+ shape: { type: "circle" },
33
+ opacity: { value: 0.5, random: true },
34
+ size: { value: { min: 1, max: 3 }, random: true },
35
+ move: {
36
+ enable: true,
37
+ speed: 1,
38
+ direction: "none",
39
+ random: true,
40
+ straight: false,
41
+ outModes: { default: "out" },
42
+ },
43
+ },
44
+ interactivity: {
45
+ events: {
46
+ onHover: { enable: true, mode: "bubble" },
47
+ },
48
+ modes: {
49
+ bubble: { distance: 200, size: 6, duration: 2, opacity: 0.8 },
50
+ },
51
+ },
52
+ detectRetina: true,
53
+ };
54
+
55
+ function validateEmail(value: string): boolean {
56
+ return /^[^\s@]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/.test(value);
57
+ }
58
+
59
+ function handleSubmit(e: Event) {
60
+ e.preventDefault();
61
+ let valid = true;
62
+
63
+ if (!email() || !validateEmail(email())) {
64
+ setEmailError("Please enter a valid email address");
65
+ valid = false;
66
+ } else {
67
+ setEmailError("");
68
+ }
69
+
70
+ if (!password() || password().length < 6) {
71
+ setPasswordError("Password must be at least 6 characters");
72
+ valid = false;
73
+ } else {
74
+ setPasswordError("");
75
+ }
76
+
77
+ if (!isLogin() && password() !== confirm()) {
78
+ setConfirmError("Passwords do not match");
79
+ valid = false;
80
+ } else {
81
+ setConfirmError("");
82
+ }
83
+
84
+ if (!valid) return;
85
+
86
+ if (isLogin()) {
87
+ alert("Logged in successfully! (demo)");
88
+ } else {
89
+ alert("Account created successfully! (demo)");
90
+ setIsLogin(true);
91
+ }
92
+
93
+ setEmail("");
94
+ setPassword("");
95
+ setConfirm("");
96
+ }
97
+
98
+ function toggleMode() {
99
+ setIsLogin(!isLogin());
100
+ setEmailError("");
101
+ setPasswordError("");
102
+ setConfirmError("");
103
+ }
104
+
105
+ function toggleTheme() {
106
+ setTheme(theme() === "dark" ? "light" : "dark");
107
+ localStorage.setItem("theme", theme());
108
+ }
109
+
110
+ return (
111
+ <>
112
+ <div class="auth-container">
113
+ <div class="auth-card">
114
+ <div class="auth-header">
115
+ <h1>{isLogin() ? "Login" : "Register"}</h1>
116
+ <button class="theme-btn" onClick={toggleTheme} aria-label="Toggle theme">
117
+ {theme() === "dark" ? "🌙" : "☀️"}
118
+ </button>
119
+ </div>
120
+ <form onSubmit={handleSubmit} novalidate>
121
+ <div class="form-group">
122
+ <label for="email">Email</label>
123
+ <input
124
+ type="email"
125
+ id="email"
126
+ placeholder="you@example.com"
127
+ value={email()}
128
+ onInput={(e) => setEmail(e.currentTarget.value)}
129
+ required
130
+ />
131
+ <span class="error">{emailError()}</span>
132
+ </div>
133
+ <div class="form-group">
134
+ <label for="password">Password</label>
135
+ <input
136
+ type="password"
137
+ id="password"
138
+ placeholder="Enter password"
139
+ value={password()}
140
+ onInput={(e) => setPassword(e.currentTarget.value)}
141
+ required
142
+ />
143
+ <span class="error">{passwordError()}</span>
144
+ </div>
145
+ <div class="form-group" style={{ display: isLogin() ? "none" : "block" }}>
146
+ <label for="confirmPassword">Confirm Password</label>
147
+ <input
148
+ type="password"
149
+ id="confirmPassword"
150
+ placeholder="Confirm password"
151
+ value={confirm()}
152
+ onInput={(e) => setConfirm(e.currentTarget.value)}
153
+ />
154
+ <span class="error">{confirmError()}</span>
155
+ </div>
156
+ <button type="submit">{isLogin() ? "Sign In" : "Create Account"}</button>
157
+ </form>
158
+ <p class="toggle-text">
159
+ <span>{isLogin() ? "Don't have an account?" : "Already have an account?"}</span>
160
+ <a href="#" onClick={(e) => { e.preventDefault(); toggleMode(); }}>
161
+ {isLogin() ? "Register" : "Login"}
162
+ </a>
163
+ </p>
164
+ </div>
165
+ </div>
166
+ <Particles id="tsparticles" options={options} />
167
+ </>
168
+ );
169
+ }
@@ -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,9 @@
1
+ import "./index.css";
2
+ import { render } from "solid-js/web";
3
+ import App from "./App";
4
+
5
+ const root = document.getElementById("root");
6
+
7
+ if (root) {
8
+ render(() => <App />, root);
9
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "scripts": {
6
+ "dev": "stencil build --dev --watch --serve",
7
+ "build": "stencil build",
8
+ "preview": "stencil build --serve"
9
+ },
10
+ "dependencies": {
11
+ "@stencil/core": "^4.43.5",
12
+ "@tsparticles/stencil": "^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": "^6.0.3"
19
+ }
20
+ }