@seaverse/auth-sdk 0.1.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.
package/README.md ADDED
@@ -0,0 +1,399 @@
1
+ # @seaverseai/auth-sdk
2
+
3
+ SeaVerse 认证 SDK - 提供完整的用户认证功能和精美的登录注册 UI 组件
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@seaverseai/auth-sdk.svg)](https://www.npmjs.com/package/@seaverseai/auth-sdk)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ ```bash
21
+ npm install @seaverseai/auth-sdk
22
+ ```
23
+
24
+ ## 快速开始
25
+
26
+ ### 1. API 客户端使用
27
+
28
+ ```typescript
29
+ import { SeaVerseBackendAPIClient } from '@seaverseai/auth-sdk';
30
+
31
+ const client = new SeaVerseBackendAPIClient({
32
+ baseURL: 'https://web.seaverse.dev',
33
+ });
34
+
35
+ // 注册
36
+ const registerResult = await client.register({
37
+ email: 'user@example.com',
38
+ password: 'password123',
39
+ });
40
+
41
+ // 登录
42
+ const loginResult = await client.login({
43
+ email: 'user@example.com',
44
+ password: 'password123',
45
+ });
46
+
47
+ // 保存 token
48
+ localStorage.setItem('token', loginResult.token);
49
+
50
+ // 获取当前用户
51
+ const user = await client.getCurrentUser();
52
+
53
+ // 登出
54
+ await client.logout();
55
+ ```
56
+
57
+ ### 2. 使用登录弹窗 UI
58
+
59
+ ```typescript
60
+ import { SeaVerseBackendAPIClient, createAuthModal } from '@seaverseai/auth-sdk';
61
+ import '@seaverseai/auth-sdk/src/auth-modal.css';
62
+
63
+ const client = new SeaVerseBackendAPIClient({
64
+ baseURL: 'https://web.seaverse.dev',
65
+ });
66
+
67
+ const authModal = createAuthModal({
68
+ client,
69
+ theme: 'dark', // 'dark' 或 'light'
70
+
71
+ onLoginSuccess: (token, user) => {
72
+ localStorage.setItem('token', token);
73
+ console.log('登录成功:', user);
74
+ },
75
+
76
+ onSignupSuccess: (token, user) => {
77
+ localStorage.setItem('token', token);
78
+ console.log('注册成功:', user);
79
+ },
80
+
81
+ onError: (error) => {
82
+ console.error('错误:', error.message);
83
+ },
84
+ });
85
+
86
+ // 显示登录界面
87
+ authModal.show('login');
88
+
89
+ // 显示注册界面
90
+ authModal.show('signup');
91
+ ```
92
+
93
+ ## OAuth 第三方登录
94
+
95
+ ### 1. 配置 OAuth
96
+
97
+ ```typescript
98
+ const authModal = createAuthModal({
99
+ client,
100
+
101
+ oauthConfig: {
102
+ google: {
103
+ clientId: 'YOUR_GOOGLE_CLIENT_ID',
104
+ redirectUri: 'https://yourdomain.com/auth/callback',
105
+ },
106
+ discord: {
107
+ clientId: 'YOUR_DISCORD_CLIENT_ID',
108
+ redirectUri: 'https://yourdomain.com/auth/callback',
109
+ },
110
+ github: {
111
+ clientId: 'YOUR_GITHUB_CLIENT_ID',
112
+ redirectUri: 'https://yourdomain.com/auth/callback',
113
+ },
114
+ },
115
+
116
+ onLoginSuccess: (token, user) => {
117
+ localStorage.setItem('token', token);
118
+ },
119
+ });
120
+
121
+ authModal.show('login');
122
+ ```
123
+
124
+ ### 2. 处理 OAuth 回调
125
+
126
+ 在你的回调页面(如 `/auth/callback`)添加:
127
+
128
+ ```typescript
129
+ import { SeaVerseBackendAPIClient, AuthModal } from '@seaverseai/auth-sdk';
130
+
131
+ const client = new SeaVerseBackendAPIClient({
132
+ baseURL: 'https://web.seaverse.dev',
133
+ });
134
+
135
+ // 自动处理 OAuth 回调
136
+ AuthModal.handleOAuthCallbackFromUrl(client, {
137
+ onLoginSuccess: (token, user) => {
138
+ localStorage.setItem('token', token);
139
+ window.location.href = '/dashboard';
140
+ },
141
+ onError: (error) => {
142
+ console.error('登录失败:', error);
143
+ window.location.href = '/';
144
+ },
145
+ });
146
+ ```
147
+
148
+ ### 3. 获取 OAuth Client ID
149
+
150
+ | 平台 | 配置地址 |
151
+ |------|---------|
152
+ | Google | https://console.cloud.google.com/ → 创建 OAuth 客户端 ID |
153
+ | Discord | https://discord.com/developers/applications → OAuth2 设置 |
154
+ | GitHub | https://github.com/settings/developers → OAuth Apps |
155
+
156
+ **重要**:在 OAuth 应用中配置的 Redirect URI 必须与代码中的 `redirectUri` 完全一致。
157
+
158
+ ## API 参考
159
+
160
+ ### 认证方法
161
+
162
+ ```typescript
163
+ // 注册
164
+ await client.register({ email, password, invitationCode? })
165
+
166
+ // 登录
167
+ await client.login({ email, password })
168
+
169
+ // 获取当前用户
170
+ await client.getCurrentUser()
171
+
172
+ // 登出
173
+ await client.logout()
174
+
175
+ // 忘记密码
176
+ await client.forgotPassword({ email })
177
+
178
+ // 重置密码
179
+ await client.resetPassword({ token, newPassword })
180
+ ```
181
+
182
+ ### OAuth 方法
183
+
184
+ ```typescript
185
+ // Google 登录
186
+ await client.googleCodeToToken({ code })
187
+
188
+ // Discord 登录
189
+ await client.discordCodeToToken({ code })
190
+
191
+ // GitHub 登录
192
+ await client.githubCodeToToken({ code })
193
+
194
+ // 解绑账号
195
+ await client.unlinkGoogle()
196
+ await client.unlinkDiscord()
197
+ await client.unlinkGithub()
198
+ ```
199
+
200
+ ### UI 方法
201
+
202
+ ```typescript
203
+ // 显示登录/注册
204
+ authModal.show('login') // 或 'signup'
205
+
206
+ // 隐藏弹窗
207
+ authModal.hide()
208
+
209
+ // 销毁弹窗
210
+ authModal.destroy()
211
+ ```
212
+
213
+ ## 类型定义
214
+
215
+ ```typescript
216
+ // 用户信息
217
+ interface User {
218
+ id?: string;
219
+ email?: string;
220
+ username?: string;
221
+ emailVerified?: boolean;
222
+ googleId?: string;
223
+ discordId?: string;
224
+ githubId?: string;
225
+ }
226
+
227
+ // 登录响应
228
+ interface LoginResponse {
229
+ token: string;
230
+ user: User;
231
+ }
232
+
233
+ // 注册响应
234
+ interface RegisterResponse {
235
+ success: boolean;
236
+ message: string;
237
+ userId: string;
238
+ }
239
+
240
+ // OAuth 配置
241
+ interface OAuthConfig {
242
+ google?: {
243
+ clientId: string;
244
+ redirectUri: string;
245
+ scope?: string; // 默认: 'openid email profile'
246
+ };
247
+ discord?: {
248
+ clientId: string;
249
+ redirectUri: string;
250
+ scope?: string; // 默认: 'identify email'
251
+ };
252
+ github?: {
253
+ clientId: string;
254
+ redirectUri: string;
255
+ scope?: string; // 默认: 'read:user user:email'
256
+ };
257
+ }
258
+ ```
259
+
260
+ ## 完整示例
261
+
262
+ ```html
263
+ <!DOCTYPE html>
264
+ <html>
265
+ <head>
266
+ <meta charset="UTF-8">
267
+ <title>登录示例</title>
268
+ <link rel="stylesheet" href="node_modules/@seaverseai/auth-sdk/src/auth-modal.css">
269
+ </head>
270
+ <body>
271
+ <h1>欢迎</h1>
272
+ <button id="loginBtn">登录</button>
273
+
274
+ <script type="module">
275
+ import { SeaVerseBackendAPIClient, createAuthModal } from '@seaverseai/auth-sdk';
276
+
277
+ const client = new SeaVerseBackendAPIClient({
278
+ baseURL: 'https://web.seaverse.dev',
279
+ });
280
+
281
+ const authModal = createAuthModal({
282
+ client,
283
+ theme: 'dark',
284
+
285
+ oauthConfig: {
286
+ google: {
287
+ clientId: 'YOUR_GOOGLE_CLIENT_ID',
288
+ redirectUri: window.location.origin + '/callback.html',
289
+ },
290
+ },
291
+
292
+ onLoginSuccess: (token, user) => {
293
+ localStorage.setItem('token', token);
294
+ alert('登录成功: ' + user.email);
295
+ },
296
+ });
297
+
298
+ document.getElementById('loginBtn').onclick = () => {
299
+ authModal.show('login');
300
+ };
301
+ </script>
302
+ </body>
303
+ </html>
304
+ ```
305
+
306
+ ## React 集成
307
+
308
+ ```tsx
309
+ import { useEffect, useState } from 'react';
310
+ import { SeaVerseBackendAPIClient, createAuthModal } from '@seaverseai/auth-sdk';
311
+ import '@seaverseai/auth-sdk/src/auth-modal.css';
312
+
313
+ function App() {
314
+ const [authModal, setAuthModal] = useState(null);
315
+
316
+ useEffect(() => {
317
+ const client = new SeaVerseBackendAPIClient({
318
+ baseURL: 'https://web.seaverse.dev',
319
+ });
320
+
321
+ const modal = createAuthModal({
322
+ client,
323
+ onLoginSuccess: (token) => {
324
+ localStorage.setItem('token', token);
325
+ },
326
+ });
327
+
328
+ setAuthModal(modal);
329
+ return () => modal?.destroy();
330
+ }, []);
331
+
332
+ return (
333
+ <button onClick={() => authModal?.show('login')}>
334
+ 登录
335
+ </button>
336
+ );
337
+ }
338
+ ```
339
+
340
+ ## 常见问题
341
+
342
+ ### OAuth redirect_uri_mismatch 错误?
343
+
344
+ 确保 OAuth 应用配置中的重定向 URI 与代码中完全一致(包括协议、域名、端口、路径)。
345
+
346
+ ### 如何自动携带 Token?
347
+
348
+ ```typescript
349
+ import { AuthFactory } from '@seaverseai/auth-sdk';
350
+
351
+ const client = new SeaVerseBackendAPIClient({
352
+ baseURL: 'https://web.seaverse.dev',
353
+ auth: AuthFactory.create({
354
+ type: 'jwt',
355
+ credentials: {
356
+ type: 'jwt',
357
+ token: localStorage.getItem('token'),
358
+ },
359
+ }),
360
+ });
361
+ ```
362
+
363
+ ### 如何只启用部分 OAuth 登录?
364
+
365
+ 只配置需要的平台即可,未配置的平台按钮点击时会提示错误:
366
+
367
+ ```typescript
368
+ oauthConfig: {
369
+ google: { /* ... */ },
370
+ // 不配置 discord 和 github
371
+ }
372
+ ```
373
+
374
+ ### 本地开发如何测试 OAuth?
375
+
376
+ 大多数 OAuth 提供商允许 `http://localhost:PORT` 作为开发环境的重定向 URI。
377
+
378
+ ## 开发
379
+
380
+ ```bash
381
+ # 安装依赖
382
+ pnpm install
383
+
384
+ # 构建
385
+ pnpm run build
386
+
387
+ # 测试
388
+ pnpm test
389
+ ```
390
+
391
+ ## License
392
+
393
+ MIT © SeaVerse Team
394
+
395
+ ## 技术支持
396
+
397
+ - 📧 Email: support@seaverse.com
398
+ - 🐛 Issues: https://github.com/seaverseai/sv-sdk/issues
399
+ - 📖 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}}