@seaverseai/auth-sdk 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +417 -0
- package/dist/auth-modal.css +1 -0
- package/dist/index.cjs +2566 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +998 -0
- package/dist/index.js +2534 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# @seaverseai/auth-sdk
|
|
2
|
+
|
|
3
|
+
SeaVerse 认证 SDK - 提供完整的用户认证功能和精美的登录注册 UI 组件
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@seaverseai/auth-sdk)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## 功能特性
|
|
9
|
+
|
|
10
|
+
- 🔐 **邮箱登录/注册** - 传统用户名密码认证
|
|
11
|
+
- 🌐 **OAuth 第三方登录** - 支持 Google、Discord、GitHub
|
|
12
|
+
- 🎨 **精美登录 UI** - 开箱即用的登录弹窗组件
|
|
13
|
+
- 📱 **完全响应式** - 适配移动端/平板/桌面
|
|
14
|
+
- 🎭 **主题支持** - 暗色/亮色主题切换
|
|
15
|
+
- ⚡ **TypeScript** - 完整的类型定义
|
|
16
|
+
- 🔒 **安全** - 内置 CSRF 保护
|
|
17
|
+
- 🌍 **多环境支持** - 一键切换生产/测试/开发环境
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @seaverseai/auth-sdk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 快速开始
|
|
26
|
+
|
|
27
|
+
### 1. API 客户端使用
|
|
28
|
+
|
|
29
|
+
#### 方式 A:指定环境(推荐)
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { SeaVerseBackendAPIClient } from '@seaverseai/auth-sdk';
|
|
33
|
+
|
|
34
|
+
// 正式环境
|
|
35
|
+
const client = new SeaVerseBackendAPIClient({
|
|
36
|
+
environment: 'production', // 'production' | 'staging' | 'development' | 'local'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// 或者测试环境
|
|
40
|
+
const client = new SeaVerseBackendAPIClient({
|
|
41
|
+
environment: 'staging',
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### 方式 B:自动检测环境
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { SeaVerseBackendAPIClient } from '@seaverseai/auth-sdk';
|
|
49
|
+
|
|
50
|
+
// SDK 会根据当前域名自动选择环境
|
|
51
|
+
const client = new SeaVerseBackendAPIClient();
|
|
52
|
+
|
|
53
|
+
// 注册
|
|
54
|
+
const registerResult = await client.register({
|
|
55
|
+
email: 'user@example.com',
|
|
56
|
+
password: 'password123',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// 登录
|
|
60
|
+
const loginResult = await client.login({
|
|
61
|
+
email: 'user@example.com',
|
|
62
|
+
password: 'password123',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// 保存 token
|
|
66
|
+
localStorage.setItem('token', loginResult.token);
|
|
67
|
+
|
|
68
|
+
// 获取当前用户
|
|
69
|
+
const user = await client.getCurrentUser();
|
|
70
|
+
|
|
71
|
+
// 登出
|
|
72
|
+
await client.logout();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. 使用登录弹窗 UI
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { SeaVerseBackendAPIClient, createAuthModal } from '@seaverseai/auth-sdk';
|
|
79
|
+
import '@seaverseai/auth-sdk/src/auth-modal.css';
|
|
80
|
+
|
|
81
|
+
const client = new SeaVerseBackendAPIClient({
|
|
82
|
+
baseURL: 'https://account-hub.sg.seaverse.dev',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const authModal = createAuthModal({
|
|
86
|
+
client,
|
|
87
|
+
theme: 'dark', // 'dark' 或 'light'
|
|
88
|
+
|
|
89
|
+
onLoginSuccess: (token, user) => {
|
|
90
|
+
localStorage.setItem('token', token);
|
|
91
|
+
console.log('登录成功:', user);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
onSignupSuccess: (token, user) => {
|
|
95
|
+
localStorage.setItem('token', token);
|
|
96
|
+
console.log('注册成功:', user);
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
onError: (error) => {
|
|
100
|
+
console.error('错误:', error.message);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// 显示登录界面
|
|
105
|
+
authModal.show('login');
|
|
106
|
+
|
|
107
|
+
// 显示注册界面
|
|
108
|
+
authModal.show('signup');
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## OAuth 第三方登录
|
|
112
|
+
|
|
113
|
+
### 1. 配置 OAuth
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const authModal = createAuthModal({
|
|
117
|
+
client,
|
|
118
|
+
|
|
119
|
+
oauthConfig: {
|
|
120
|
+
google: {
|
|
121
|
+
clientId: 'YOUR_GOOGLE_CLIENT_ID',
|
|
122
|
+
redirectUri: 'https://yourdomain.com/auth/callback',
|
|
123
|
+
},
|
|
124
|
+
discord: {
|
|
125
|
+
clientId: 'YOUR_DISCORD_CLIENT_ID',
|
|
126
|
+
redirectUri: 'https://yourdomain.com/auth/callback',
|
|
127
|
+
},
|
|
128
|
+
github: {
|
|
129
|
+
clientId: 'YOUR_GITHUB_CLIENT_ID',
|
|
130
|
+
redirectUri: 'https://yourdomain.com/auth/callback',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
onLoginSuccess: (token, user) => {
|
|
135
|
+
localStorage.setItem('token', token);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
authModal.show('login');
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 2. 处理 OAuth 回调
|
|
143
|
+
|
|
144
|
+
在你的回调页面(如 `/auth/callback`)添加:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { SeaVerseBackendAPIClient, AuthModal } from '@seaverseai/auth-sdk';
|
|
148
|
+
|
|
149
|
+
const client = new SeaVerseBackendAPIClient({
|
|
150
|
+
baseURL: 'https://account-hub.sg.seaverse.dev',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// 自动处理 OAuth 回调
|
|
154
|
+
AuthModal.handleOAuthCallbackFromUrl(client, {
|
|
155
|
+
onLoginSuccess: (token, user) => {
|
|
156
|
+
localStorage.setItem('token', token);
|
|
157
|
+
window.location.href = '/dashboard';
|
|
158
|
+
},
|
|
159
|
+
onError: (error) => {
|
|
160
|
+
console.error('登录失败:', error);
|
|
161
|
+
window.location.href = '/';
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 3. 获取 OAuth Client ID
|
|
167
|
+
|
|
168
|
+
| 平台 | 配置地址 |
|
|
169
|
+
|------|---------|
|
|
170
|
+
| Google | https://console.cloud.google.com/ → 创建 OAuth 客户端 ID |
|
|
171
|
+
| Discord | https://discord.com/developers/applications → OAuth2 设置 |
|
|
172
|
+
| GitHub | https://github.com/settings/developers → OAuth Apps |
|
|
173
|
+
|
|
174
|
+
**重要**:在 OAuth 应用中配置的 Redirect URI 必须与代码中的 `redirectUri` 完全一致。
|
|
175
|
+
|
|
176
|
+
## API 参考
|
|
177
|
+
|
|
178
|
+
### 认证方法
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// 注册
|
|
182
|
+
await client.register({ email, password, invitationCode? })
|
|
183
|
+
|
|
184
|
+
// 登录
|
|
185
|
+
await client.login({ email, password })
|
|
186
|
+
|
|
187
|
+
// 获取当前用户
|
|
188
|
+
await client.getCurrentUser()
|
|
189
|
+
|
|
190
|
+
// 登出
|
|
191
|
+
await client.logout()
|
|
192
|
+
|
|
193
|
+
// 忘记密码
|
|
194
|
+
await client.forgotPassword({ email })
|
|
195
|
+
|
|
196
|
+
// 重置密码
|
|
197
|
+
await client.resetPassword({ token, newPassword })
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### OAuth 方法
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// Google 登录
|
|
204
|
+
await client.googleCodeToToken({ code })
|
|
205
|
+
|
|
206
|
+
// Discord 登录
|
|
207
|
+
await client.discordCodeToToken({ code })
|
|
208
|
+
|
|
209
|
+
// GitHub 登录
|
|
210
|
+
await client.githubCodeToToken({ code })
|
|
211
|
+
|
|
212
|
+
// 解绑账号
|
|
213
|
+
await client.unlinkGoogle()
|
|
214
|
+
await client.unlinkDiscord()
|
|
215
|
+
await client.unlinkGithub()
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### UI 方法
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// 显示登录/注册
|
|
222
|
+
authModal.show('login') // 或 'signup'
|
|
223
|
+
|
|
224
|
+
// 隐藏弹窗
|
|
225
|
+
authModal.hide()
|
|
226
|
+
|
|
227
|
+
// 销毁弹窗
|
|
228
|
+
authModal.destroy()
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## 类型定义
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// 用户信息
|
|
235
|
+
interface User {
|
|
236
|
+
id?: string;
|
|
237
|
+
email?: string;
|
|
238
|
+
username?: string;
|
|
239
|
+
emailVerified?: boolean;
|
|
240
|
+
googleId?: string;
|
|
241
|
+
discordId?: string;
|
|
242
|
+
githubId?: string;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 登录响应
|
|
246
|
+
interface LoginResponse {
|
|
247
|
+
token: string;
|
|
248
|
+
user: User;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 注册响应
|
|
252
|
+
interface RegisterResponse {
|
|
253
|
+
success: boolean;
|
|
254
|
+
message: string;
|
|
255
|
+
userId: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// OAuth 配置
|
|
259
|
+
interface OAuthConfig {
|
|
260
|
+
google?: {
|
|
261
|
+
clientId: string;
|
|
262
|
+
redirectUri: string;
|
|
263
|
+
scope?: string; // 默认: 'openid email profile'
|
|
264
|
+
};
|
|
265
|
+
discord?: {
|
|
266
|
+
clientId: string;
|
|
267
|
+
redirectUri: string;
|
|
268
|
+
scope?: string; // 默认: 'identify email'
|
|
269
|
+
};
|
|
270
|
+
github?: {
|
|
271
|
+
clientId: string;
|
|
272
|
+
redirectUri: string;
|
|
273
|
+
scope?: string; // 默认: 'read:user user:email'
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## 完整示例
|
|
279
|
+
|
|
280
|
+
```html
|
|
281
|
+
<!DOCTYPE html>
|
|
282
|
+
<html>
|
|
283
|
+
<head>
|
|
284
|
+
<meta charset="UTF-8">
|
|
285
|
+
<title>登录示例</title>
|
|
286
|
+
<link rel="stylesheet" href="node_modules/@seaverseai/auth-sdk/src/auth-modal.css">
|
|
287
|
+
</head>
|
|
288
|
+
<body>
|
|
289
|
+
<h1>欢迎</h1>
|
|
290
|
+
<button id="loginBtn">登录</button>
|
|
291
|
+
|
|
292
|
+
<script type="module">
|
|
293
|
+
import { SeaVerseBackendAPIClient, createAuthModal } from '@seaverseai/auth-sdk';
|
|
294
|
+
|
|
295
|
+
const client = new SeaVerseBackendAPIClient({
|
|
296
|
+
baseURL: 'https://account-hub.sg.seaverse.dev',
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const authModal = createAuthModal({
|
|
300
|
+
client,
|
|
301
|
+
theme: 'dark',
|
|
302
|
+
|
|
303
|
+
oauthConfig: {
|
|
304
|
+
google: {
|
|
305
|
+
clientId: 'YOUR_GOOGLE_CLIENT_ID',
|
|
306
|
+
redirectUri: window.location.origin + '/callback.html',
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
onLoginSuccess: (token, user) => {
|
|
311
|
+
localStorage.setItem('token', token);
|
|
312
|
+
alert('登录成功: ' + user.email);
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
document.getElementById('loginBtn').onclick = () => {
|
|
317
|
+
authModal.show('login');
|
|
318
|
+
};
|
|
319
|
+
</script>
|
|
320
|
+
</body>
|
|
321
|
+
</html>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## React 集成
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
import { useEffect, useState } from 'react';
|
|
328
|
+
import { SeaVerseBackendAPIClient, createAuthModal } from '@seaverseai/auth-sdk';
|
|
329
|
+
import '@seaverseai/auth-sdk/src/auth-modal.css';
|
|
330
|
+
|
|
331
|
+
function App() {
|
|
332
|
+
const [authModal, setAuthModal] = useState(null);
|
|
333
|
+
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
const client = new SeaVerseBackendAPIClient({
|
|
336
|
+
baseURL: 'https://account-hub.sg.seaverse.dev',
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const modal = createAuthModal({
|
|
340
|
+
client,
|
|
341
|
+
onLoginSuccess: (token) => {
|
|
342
|
+
localStorage.setItem('token', token);
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
setAuthModal(modal);
|
|
347
|
+
return () => modal?.destroy();
|
|
348
|
+
}, []);
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<button onClick={() => authModal?.show('login')}>
|
|
352
|
+
登录
|
|
353
|
+
</button>
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## 常见问题
|
|
359
|
+
|
|
360
|
+
### OAuth redirect_uri_mismatch 错误?
|
|
361
|
+
|
|
362
|
+
确保 OAuth 应用配置中的重定向 URI 与代码中完全一致(包括协议、域名、端口、路径)。
|
|
363
|
+
|
|
364
|
+
### 如何自动携带 Token?
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
import { AuthFactory } from '@seaverseai/auth-sdk';
|
|
368
|
+
|
|
369
|
+
const client = new SeaVerseBackendAPIClient({
|
|
370
|
+
baseURL: 'https://account-hub.sg.seaverse.dev',
|
|
371
|
+
auth: AuthFactory.create({
|
|
372
|
+
type: 'jwt',
|
|
373
|
+
credentials: {
|
|
374
|
+
type: 'jwt',
|
|
375
|
+
token: localStorage.getItem('token'),
|
|
376
|
+
},
|
|
377
|
+
}),
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### 如何只启用部分 OAuth 登录?
|
|
382
|
+
|
|
383
|
+
只配置需要的平台即可,未配置的平台按钮点击时会提示错误:
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
oauthConfig: {
|
|
387
|
+
google: { /* ... */ },
|
|
388
|
+
// 不配置 discord 和 github
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### 本地开发如何测试 OAuth?
|
|
393
|
+
|
|
394
|
+
大多数 OAuth 提供商允许 `http://localhost:PORT` 作为开发环境的重定向 URI。
|
|
395
|
+
|
|
396
|
+
## 开发
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
# 安装依赖
|
|
400
|
+
pnpm install
|
|
401
|
+
|
|
402
|
+
# 构建
|
|
403
|
+
pnpm run build
|
|
404
|
+
|
|
405
|
+
# 测试
|
|
406
|
+
pnpm test
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## License
|
|
410
|
+
|
|
411
|
+
MIT © SeaVerse Team
|
|
412
|
+
|
|
413
|
+
## 技术支持
|
|
414
|
+
|
|
415
|
+
- 📧 Email: support@seaverse.com
|
|
416
|
+
- 🐛 Issues: https://github.com/seaverseai/sv-sdk/issues
|
|
417
|
+
- 📖 Docs: https://docs.seaverse.dev
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--font-display:-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;--font-sans:-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;--color-neon-green:#10b981;--color-text-primary:#fff;--color-text-secondary:#a1a1aa;--color-text-tertiary:#71717a;--gradient-neon:linear-gradient(135deg,#10b981,#06b6d4)}.auth-modal{align-items:center;animation:modalFadeIn .3s ease-out;display:flex;inset:0;justify-content:center;overflow:hidden;padding:20px;position:fixed;z-index:9999}.auth-modal.hidden{display:none}@keyframes modalFadeIn{0%{opacity:0}to{opacity:1}}.auth-modal-backdrop{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);background:rgba(0,0,0,.85);inset:0;position:absolute}.auth-modal-content{animation:modalSlideUp .4s cubic-bezier(.4,0,.2,1);background:#0d0d0d;border:1px solid hsla(0,0%,100%,.08);border-radius:20px;box-shadow:0 25px 80px rgba(0,0,0,.8);display:grid;grid-template-columns:1fr 1fr;max-height:90vh;max-width:900px;overflow:hidden;position:relative;transition:max-width .3s ease;width:100%;z-index:10}.auth-modal-content:has(#loginForm:not(.hidden)){grid-template-columns:1fr auto;max-width:none;width:auto}.auth-modal-content:has(#loginForm:not(.hidden)) .auth-modal-right{min-width:0;width:auto}.auth-modal-content:has(#authMessage:not(.hidden)){grid-template-columns:1fr;max-width:480px}.auth-modal-content:has(#authMessage:not(.hidden)) .auth-modal-left{display:none}@keyframes modalSlideUp{0%{opacity:0;transform:translateY(30px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.auth-modal-left{background:#000;border-right:1px solid hsla(0,0%,100%,.05);display:flex;flex-direction:column;overflow:hidden;padding:32px;position:relative}.auth-modal-left:before{animation:meshRotate 20s linear infinite;background:radial-gradient(at 0 0,rgba(99,102,241,.3) 0,transparent 50%),radial-gradient(at 50% 0,rgba(139,92,246,.3) 0,transparent 50%),radial-gradient(at 100% 0,rgba(236,72,153,.3) 0,transparent 50%),radial-gradient(at 0 50%,rgba(16,185,129,.3) 0,transparent 50%),radial-gradient(at 100% 50%,rgba(244,63,94,.3) 0,transparent 50%),radial-gradient(at 0 100%,rgba(236,72,153,.3) 0,transparent 50%),radial-gradient(at 100% 100%,rgba(139,92,246,.3) 0,transparent 50%);content:"";filter:blur(80px);height:200%;inset:-50%;opacity:.6;position:absolute;width:200%}@keyframes meshRotate{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.auth-modal-left:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cfilter id='a'%3E%3CfeTurbulence baseFrequency='.65' numOctaves='3' stitchTiles='stitch' type='fractalNoise'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)' opacity='.05'/%3E%3C/svg%3E");content:"";inset:0;mix-blend-mode:overlay;opacity:.4;pointer-events:none;position:absolute}.auth-modal-logo{align-items:center;display:flex;gap:10px;position:relative;z-index:10}.auth-modal-logo .logo-icon{filter:drop-shadow(0 0 10px rgba(16,185,129,.3));height:28px;width:28px}.auth-modal-logo .logo-text{color:#fff;font-family:var(--font-display);font-size:18px;font-weight:700;text-shadow:0 0 20px rgba(0,0,0,.5)}.auth-modal-decoration{inset:0;overflow:hidden;pointer-events:none;position:absolute}.auth-modal-decoration .glow-orb-1{animation:breathe 8s ease-in-out infinite;background:radial-gradient(circle,hsla(0,0%,100%,.05) 0,transparent 70%);border-radius:50%;filter:blur(60px);height:400px;left:50%;pointer-events:none;position:absolute;top:50%;transform:translate(-50%,-50%);width:400px}@keyframes breathe{0%,to{opacity:.5;transform:translate(-50%,-50%) scale(1)}50%{opacity:.8;transform:translate(-50%,-50%) scale(1.2)}}.auth-modal-decoration .glow-orb-2{animation:breathe 10s ease-in-out infinite;animation-delay:2s;background:radial-gradient(circle,rgba(139,92,246,.08) 0,transparent 70%);border-radius:50%;filter:blur(50px);height:300px;pointer-events:none;position:absolute;right:-10%;top:20%;width:300px}.auth-modal-branding{margin-bottom:48px;margin-top:auto;position:relative;z-index:10}.auth-modal-branding .branding-title{color:#fff;font-family:var(--font-display);font-size:36px;font-weight:700;letter-spacing:-.02em;line-height:1.15;margin-bottom:12px;text-shadow:0 2px 10px rgba(0,0,0,.3)}.auth-modal-branding .branding-subtitle{color:hsla(0,0%,100%,.8);font-size:14px;line-height:1.6;max-width:260px}.auth-modal-right{background:#121212;display:flex;flex-direction:column;max-height:90vh;overflow-y:auto;padding:56px 48px 42px;position:relative}.auth-modal-right::-webkit-scrollbar{width:6px}.auth-modal-right::-webkit-scrollbar-track{background:transparent}.auth-modal-right::-webkit-scrollbar-thumb{background:hsla(0,0%,100%,.1);border-radius:3px}.auth-modal-close{align-items:center;background:transparent;border:none;border-radius:8px;color:hsla(0,0%,100%,.5);cursor:pointer;display:flex;height:36px;justify-content:center;position:absolute;right:16px;top:16px;transition:all .2s ease;width:36px;z-index:20}.auth-modal-close:hover{background:hsla(0,0%,100%,.1);color:#fff}.auth-form-view{animation:formFadeIn .3s ease-out}#loginForm .form-input{width:360px}@keyframes formFadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.auth-form-view.hidden{display:none}.auth-form-header{margin-bottom:28px;text-align:center}.auth-form-title{color:#fff;font-family:var(--font-display);font-size:26px;font-weight:700;letter-spacing:-.02em;margin-bottom:8px}.auth-form-subtitle{color:var(--color-text-secondary);font-size:14px}.auth-form{gap:16px}.auth-form,.form-group{display:flex;flex-direction:column}.form-group{gap:6px}.form-label{font-size:12px;font-weight:500;letter-spacing:.02em;opacity:.9;text-transform:uppercase}.form-input,.form-label{color:var(--color-text-primary)}.form-input{background:rgba(30,30,30,.8);border:1px solid hsla(0,0%,100%,.1);border-radius:8px;font-family:var(--font-sans);font-size:14px;height:40px;outline:none;padding:0 14px;transition:all .2s ease;width:100%}.form-input::placeholder{color:hsla(0,0%,100%,.35)}.form-input:hover{border-color:hsla(0,0%,100%,.15)}.form-input:focus{background:rgba(30,30,30,.9);border-color:#10b981;box-shadow:0 0 0 2px rgba(16,185,129,.15)}.form-input:invalid:not(:placeholder-shown){border-color:#ff006e}.input-with-icon{align-items:center;display:flex;position:relative}.input-with-icon .form-input{padding-right:40px}.input-icon-btn{align-items:center;background:transparent;border:none;border-radius:4px;color:hsla(0,0%,100%,.4);cursor:pointer;display:flex;justify-content:center;padding:6px;position:absolute;right:8px;transition:color .2s ease}.input-icon-btn:hover{background:hsla(0,0%,100%,.05);color:hsla(0,0%,100%,.7)}.input-icon-btn .hidden{display:none}.strength-text{align-items:center;color:var(--color-text-tertiary);display:flex;font-size:12px;font-weight:500;gap:6px;margin-top:4px}.form-group-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:8px}.form-group-header .form-label{margin-bottom:0}.forgot-password-link{color:hsla(0,0%,100%,.5);font-size:13px;font-weight:500;position:relative;text-decoration:none;transition:all .25s ease}.forgot-password-link:after{background:#10b981;bottom:-2px;content:"";height:1px;left:0;position:absolute;transition:width .3s ease;width:0}.forgot-password-link:hover{color:#10b981}.forgot-password-link:hover:after{width:100%}.link-primary{color:var(--color-neon-green);font-weight:600;position:relative;text-decoration:none;text-shadow:0 0 10px rgba(0,255,136,.3);transition:all .2s ease}.link-primary:after{background:var(--color-neon-green);bottom:-2px;content:"";height:1px;left:0;position:absolute;transform:scaleX(0);transform-origin:right;transition:transform .3s ease;width:100%}.link-primary:hover{color:#0f8;text-shadow:0 0 20px rgba(0,255,136,.5)}.link-primary:hover:after{transform:scaleX(1);transform-origin:left}.btn-auth-primary{align-items:center;background:var(--gradient-neon);border:none;border-radius:10px;box-shadow:0 4px 12px rgba(16,185,129,.25);color:#000;cursor:pointer;display:flex;font-family:var(--font-sans);font-size:14px;font-weight:600;gap:8px;height:44px;justify-content:center;margin-top:8px;overflow:hidden;padding:0 24px;position:relative;transition:all .3s cubic-bezier(.4,0,.2,1);width:100%}.btn-auth-primary:before{background:linear-gradient(135deg,transparent,hsla(0,0%,100%,.2) 50%,transparent);content:"";inset:0;opacity:0;position:absolute;transition:opacity .3s ease}.btn-auth-primary:hover{box-shadow:0 8px 20px rgba(16,185,129,.35);transform:translateY(-2px)}.btn-auth-primary:hover:before{opacity:1}.btn-auth-primary:active{box-shadow:0 2px 8px rgba(16,185,129,.25);transform:translateY(0)}.btn-auth-primary:disabled{cursor:not-allowed;opacity:.6;transform:none}.btn-auth-primary .btn-text{position:relative;z-index:2}.btn-auth-primary .btn-loader{align-items:center;display:flex;justify-content:center;position:relative;z-index:2}.btn-auth-primary .btn-loader.hidden{display:none}.spinner{animation:spin .8s linear infinite;fill:none;height:20px;stroke-width:3;width:20px}@keyframes spin{to{transform:rotate(1turn)}}.spinner-track{stroke:rgba(0,0,0,.2)}.spinner-circle{animation:spinDash 1.2s ease-in-out infinite;stroke:#000;stroke-dasharray:50;stroke-dashoffset:50}@keyframes spinDash{0%{stroke-dashoffset:50}50%{stroke-dashoffset:12.5}to{stroke-dashoffset:50}}.forgot-password-icon{align-items:center;display:flex;height:100px;justify-content:center;margin:0 auto 24px;position:relative;width:100px}.forgot-password-icon .icon-glow{animation:pulseGlow 3s ease-in-out infinite;background:radial-gradient(circle,rgba(16,185,129,.25) 0,transparent 70%);border-radius:50%;filter:blur(15px);inset:-10px;position:absolute}@keyframes pulseGlow{0%,to{opacity:.5;transform:scale(.95)}50%{opacity:.8;transform:scale(1.05)}}.forgot-password-icon .icon-lock{animation:floatIcon 4s ease-in-out infinite;color:#10b981;filter:drop-shadow(0 0 10px rgba(16,185,129,.4));position:relative;z-index:2}@keyframes floatIcon{0%,to{transform:translateY(0)}50%{transform:translateY(-6px)}}.btn-forgot-password{align-items:center;display:flex;gap:8px;justify-content:center}.btn-forgot-password .btn-arrow{transition:transform .3s ease}.btn-forgot-password:hover .btn-arrow{transform:translateX(4px)}.auth-footer{margin-top:24px;text-align:center}.forgot-footer{display:flex;justify-content:center}.back-to-login{align-items:center;border-radius:6px;color:var(--color-text-secondary);display:inline-flex;font-size:14px;font-weight:500;gap:8px;padding:8px 12px;text-decoration:none;transition:all .2s ease}.back-to-login:hover{background:rgba(16,185,129,.05);color:var(--color-neon-green)}.back-to-login svg{transition:transform .3s ease}.back-to-login:hover svg{transform:translateX(-4px)}.auth-message-content{align-items:center;display:flex;flex-direction:column;padding:24px;text-align:center}.message-icon{align-items:center;background:rgba(16,185,129,.1);border-radius:50%;color:#10b981;display:flex;height:80px;justify-content:center;margin-bottom:24px;width:80px}.message-icon svg{animation:successPulse 2s ease-in-out infinite;filter:drop-shadow(0 0 10px rgba(16,185,129,.3))}@keyframes successPulse{0%,to{transform:scale(1)}50%{transform:scale(1.05)}}.message-title{color:#fff;font-family:var(--font-display);font-size:24px;font-weight:700;margin-bottom:12px}.message-text{color:var(--color-text-secondary);font-size:14px;line-height:1.6;margin-bottom:24px}.divider{align-items:center;color:var(--color-text-tertiary);display:flex;font-size:11px;font-weight:600;gap:12px;letter-spacing:.08em;margin:24px 0 20px;position:relative;text-align:center}.divider:after,.divider:before{background:linear-gradient(90deg,transparent,hsla(0,0%,100%,.1),transparent);content:"";flex:1;height:1px}.social-buttons-grid{display:grid;gap:12px;grid-template-columns:1fr 1fr;margin-bottom:12px}.btn-social{align-items:center;background:rgba(30,30,30,.8);border:1px solid hsla(0,0%,100%,.1);border-radius:10px;color:var(--color-text-primary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:14px;font-weight:500;gap:10px;height:44px;justify-content:center;overflow:hidden;padding:0 16px;position:relative;transition:all .25s cubic-bezier(.4,0,.2,1)}.btn-social:before{background:linear-gradient(135deg,transparent,hsla(0,0%,100%,.05) 50%,transparent);content:"";inset:0;opacity:0;position:absolute;transition:opacity .3s ease}.btn-social:hover{background:rgba(40,40,40,.9);border-color:hsla(0,0%,100%,.2);box-shadow:0 4px 12px rgba(0,0,0,.3);transform:translateY(-2px)}.btn-social:hover:before{opacity:1}.btn-social:active{transform:translateY(0)}.btn-social svg{flex-shrink:0;height:20px;width:20px}.btn-social span{position:relative;white-space:nowrap;z-index:2}.btn-social-full{align-items:center;background:rgba(30,30,30,.8);border:1px solid hsla(0,0%,100%,.1);border-radius:10px;color:var(--color-text-primary);cursor:pointer;display:flex;font-family:var(--font-sans);font-size:14px;font-weight:500;gap:10px;height:44px;justify-content:center;overflow:hidden;padding:0 16px;position:relative;transition:all .25s cubic-bezier(.4,0,.2,1);width:100%}.btn-social-full:before{background:linear-gradient(135deg,transparent,hsla(0,0%,100%,.05) 50%,transparent);content:"";inset:0;opacity:0;position:absolute;transition:opacity .3s ease}.btn-social-full:hover{background:rgba(40,40,40,.9);border-color:hsla(0,0%,100%,.2);box-shadow:0 4px 12px rgba(0,0,0,.3);transform:translateY(-2px)}.btn-social-full:hover:before{opacity:1}.btn-social-full:active{transform:translateY(0)}.btn-social-full svg{flex-shrink:0;height:20px;width:20px}.btn-social-full span{position:relative;z-index:2}@media (max-width:768px){.auth-modal-content{border-radius:0;grid-template-columns:1fr;max-height:100vh;max-width:100%}.auth-modal-left{display:none}.auth-modal-right{max-height:100vh;padding:24px}.auth-form-title{font-size:22px}#loginForm .form-input{width:100%}.btn-auth-primary{font-size:15px;height:48px}.social-buttons-grid{gap:10px;grid-template-columns:1fr}.btn-social,.btn-social-full{height:48px}}@media (max-width:480px){.auth-modal{padding:0}.auth-modal-content{border-radius:0;max-height:100vh}.auth-modal-right{padding:20px 16px}.auth-form-header{margin-bottom:20px}}
|