@zhang_libo/resource-hub 1.0.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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +80 -0
  3. package/README.ja.md +80 -0
  4. package/README.md +79 -0
  5. package/README.zh-TW.md +80 -0
  6. package/bin/cli.js +10 -0
  7. package/dist/app.d.ts +2 -0
  8. package/dist/app.d.ts.map +1 -0
  9. package/dist/app.js +59 -0
  10. package/dist/app.js.map +1 -0
  11. package/dist/db/index.js +12 -0
  12. package/dist/db/index.js.map +1 -0
  13. package/dist/db/migrate.d.ts +3 -0
  14. package/dist/db/migrate.d.ts.map +1 -0
  15. package/dist/db/migrate.js +169 -0
  16. package/dist/db/migrate.js.map +1 -0
  17. package/dist/db/schema.d.ts +743 -0
  18. package/dist/db/schema.d.ts.map +1 -0
  19. package/dist/db/schema.js +88 -0
  20. package/dist/db/schema.js.map +1 -0
  21. package/dist/i18n.js +309 -0
  22. package/dist/i18n.js.map +1 -0
  23. package/dist/plugins/admin.d.ts +4 -0
  24. package/dist/plugins/admin.d.ts.map +1 -0
  25. package/dist/plugins/admin.js +19 -0
  26. package/dist/plugins/admin.js.map +1 -0
  27. package/dist/plugins/auth.d.ts +4 -0
  28. package/dist/plugins/auth.d.ts.map +1 -0
  29. package/dist/plugins/auth.js +35 -0
  30. package/dist/plugins/auth.js.map +1 -0
  31. package/dist/routes/auth.d.ts +4 -0
  32. package/dist/routes/auth.d.ts.map +1 -0
  33. package/dist/routes/auth.js +352 -0
  34. package/dist/routes/auth.js.map +1 -0
  35. package/dist/routes/categories.d.ts +4 -0
  36. package/dist/routes/categories.d.ts.map +1 -0
  37. package/dist/routes/categories.js +112 -0
  38. package/dist/routes/categories.js.map +1 -0
  39. package/dist/routes/config.d.ts +4 -0
  40. package/dist/routes/config.d.ts.map +1 -0
  41. package/dist/routes/config.js +227 -0
  42. package/dist/routes/config.js.map +1 -0
  43. package/dist/routes/resources.d.ts +4 -0
  44. package/dist/routes/resources.d.ts.map +1 -0
  45. package/dist/routes/resources.js +474 -0
  46. package/dist/routes/resources.js.map +1 -0
  47. package/dist/routes/tags.d.ts +4 -0
  48. package/dist/routes/tags.d.ts.map +1 -0
  49. package/dist/routes/tags.js +37 -0
  50. package/dist/routes/tags.js.map +1 -0
  51. package/dist/routes/users.d.ts +4 -0
  52. package/dist/routes/users.d.ts.map +1 -0
  53. package/dist/routes/users.js +181 -0
  54. package/dist/routes/users.js.map +1 -0
  55. package/dist/services/crypto.js +49 -0
  56. package/dist/services/crypto.js.map +1 -0
  57. package/dist/services/mail.d.ts +16 -0
  58. package/dist/services/mail.d.ts.map +1 -0
  59. package/dist/services/mail.js +33 -0
  60. package/dist/services/mail.js.map +1 -0
  61. package/dist/services/rsa.js +49 -0
  62. package/dist/services/rsa.js.map +1 -0
  63. package/dist/services/token.d.ts +9 -0
  64. package/dist/services/token.d.ts.map +1 -0
  65. package/dist/services/token.js +29 -0
  66. package/dist/services/token.js.map +1 -0
  67. package/dist/types.d.ts +80 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +2 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +73 -0
  72. package/public/admin/AdminCategories.jsx +310 -0
  73. package/public/admin/AdminConfig.jsx +254 -0
  74. package/public/admin/AdminEmail.jsx +279 -0
  75. package/public/admin/AdminTags.jsx +263 -0
  76. package/public/admin/AdminUsers.jsx +452 -0
  77. package/public/app.jsx +186 -0
  78. package/public/components/ConfirmDialog.jsx +78 -0
  79. package/public/components/DropdownSelect.jsx +281 -0
  80. package/public/components/EmailPreviewModal.jsx +104 -0
  81. package/public/components/EmptyState.jsx +50 -0
  82. package/public/components/Modal.jsx +127 -0
  83. package/public/components/PasswordStrength.jsx +45 -0
  84. package/public/components/Skeleton.jsx +68 -0
  85. package/public/components/Toast.jsx +80 -0
  86. package/public/components/TooltipIconButton.jsx +55 -0
  87. package/public/context/AppContext.jsx +314 -0
  88. package/public/features/BatchResourceModal.jsx +606 -0
  89. package/public/features/ChangePasswordModal.jsx +187 -0
  90. package/public/features/ProfileModal.jsx +170 -0
  91. package/public/features/ResourceCard.jsx +422 -0
  92. package/public/features/ResourceFormModal.jsx +915 -0
  93. package/public/features/ResourceRow.jsx +287 -0
  94. package/public/features/ResourceTimeline.jsx +472 -0
  95. package/public/hooks/useApi.jsx +26 -0
  96. package/public/hooks/useRouter.jsx +35 -0
  97. package/public/index.html +258 -0
  98. package/public/layout/AdminLayout.jsx +167 -0
  99. package/public/layout/AppLayout.jsx +119 -0
  100. package/public/layout/AuthLayout.jsx +503 -0
  101. package/public/layout/Header.jsx +543 -0
  102. package/public/layout/Sidebar.jsx +175 -0
  103. package/public/pages/AdminPage.jsx +30 -0
  104. package/public/pages/ForgotPasswordPage.jsx +93 -0
  105. package/public/pages/HomePage.jsx +2297 -0
  106. package/public/pages/LoginPage.jsx +191 -0
  107. package/public/pages/RegisterPage.jsx +137 -0
  108. package/public/pages/ResetPasswordPage.jsx +169 -0
  109. package/public/pages/SetupPage.jsx +157 -0
  110. package/public/utils/helpers.jsx +152 -0
  111. package/public/utils/i18n.jsx +1374 -0
  112. package/public/utils/preferences.jsx +220 -0
  113. package/public/utils/security.jsx +88 -0
  114. package/public/utils/theme.jsx +24 -0
  115. package/public/vendor/babel.min.js +2 -0
  116. package/public/vendor/lucide-react.min.js +9 -0
  117. package/public/vendor/react-dom.development.js +29869 -0
  118. package/public/vendor/react.development.js +3342 -0
@@ -0,0 +1,503 @@
1
+ // AuthLayout.jsx
2
+ const authStyles = {
3
+ label: {
4
+ fontSize: '13px',
5
+ fontWeight: 700,
6
+ color: 'var(--text-primary)',
7
+ display: 'block',
8
+ marginBottom: '8px',
9
+ letterSpacing: '-0.01em',
10
+ },
11
+ field: { marginBottom: '14px' },
12
+ input: (error = false, withTrailingAction = false) => ({
13
+ width: '100%',
14
+ minHeight: '48px',
15
+ padding: withTrailingAction ? '0 52px 0 14px' : '0 14px',
16
+ border: `1px solid ${error ? 'var(--danger)' : 'color-mix(in srgb, var(--control-border-strong) 72%, var(--control-border))'}`,
17
+ borderRadius: '12px',
18
+ background: 'var(--control-bg)',
19
+ color: 'var(--text-primary)',
20
+ fontSize: '14px',
21
+ fontWeight: 500,
22
+ outline: 'none',
23
+ boxSizing: 'border-box',
24
+ boxShadow: '0 1px 2px color-mix(in srgb, var(--text-primary) 4%, transparent)',
25
+ }),
26
+ passwordToggle: {
27
+ position: 'absolute',
28
+ right: '6px',
29
+ top: '50%',
30
+ transform: 'translateY(-50%)',
31
+ width: '36px',
32
+ height: '36px',
33
+ borderRadius: '10px',
34
+ border: 'none',
35
+ background: 'color-mix(in srgb, var(--brand-soft) 28%, transparent)',
36
+ color: 'var(--text-tertiary)',
37
+ cursor: 'pointer',
38
+ display: 'inline-flex',
39
+ alignItems: 'center',
40
+ justifyContent: 'center',
41
+ transition: 'background 150ms, color 150ms',
42
+ },
43
+ primaryButton: (disabled = false) => ({
44
+ width: '100%',
45
+ minHeight: '50px',
46
+ padding: '0 16px',
47
+ background: 'var(--brand)',
48
+ color: '#fff',
49
+ border: '1px solid var(--brand)',
50
+ borderRadius: '12px',
51
+ cursor: disabled ? 'not-allowed' : 'pointer',
52
+ fontSize: '15px',
53
+ fontWeight: 700,
54
+ opacity: disabled ? 0.82 : 1,
55
+ display: 'flex',
56
+ alignItems: 'center',
57
+ justifyContent: 'center',
58
+ gap: '8px',
59
+ boxShadow: '0 14px 30px color-mix(in srgb, var(--brand) 24%, transparent)',
60
+ }),
61
+ secondaryButton: (disabled = false) => ({
62
+ minHeight: '40px',
63
+ padding: '0 18px',
64
+ border: '1px solid var(--control-border)',
65
+ background: 'var(--surface-elevated)',
66
+ color: 'var(--text-primary)',
67
+ borderRadius: '10px',
68
+ cursor: disabled ? 'not-allowed' : 'pointer',
69
+ fontSize: '14px',
70
+ fontWeight: 600,
71
+ boxShadow: 'var(--shadow-control)',
72
+ opacity: disabled ? 0.72 : 1,
73
+ }),
74
+ inlineLink: (tone = 'muted') => ({
75
+ background: 'none',
76
+ border: 'none',
77
+ padding: 0,
78
+ color: tone === 'brand' ? 'var(--brand-strong)' : 'var(--text-secondary)',
79
+ cursor: 'pointer',
80
+ fontSize: '13px',
81
+ fontWeight: tone === 'brand' ? 700 : 600,
82
+ }),
83
+ footerRow: {
84
+ display: 'flex',
85
+ justifyContent: 'space-between',
86
+ alignItems: 'center',
87
+ gap: '12px',
88
+ flexWrap: 'wrap',
89
+ },
90
+ footerCenter: {
91
+ textAlign: 'center',
92
+ },
93
+ helperText: {
94
+ fontSize: '12px',
95
+ color: 'var(--text-tertiary)',
96
+ marginTop: '8px',
97
+ lineHeight: 1.5,
98
+ },
99
+ errorText: {
100
+ fontSize: '12px',
101
+ color: 'var(--danger)',
102
+ marginTop: '6px',
103
+ lineHeight: 1.5,
104
+ },
105
+ errorBanner: {
106
+ display: 'flex',
107
+ alignItems: 'center',
108
+ gap: '8px',
109
+ fontSize: '13px',
110
+ color: 'var(--danger)',
111
+ background: 'color-mix(in srgb, var(--danger) 8%, var(--surface-elevated))',
112
+ border: '1px solid color-mix(in srgb, var(--danger) 20%, var(--control-border))',
113
+ borderRadius: '12px',
114
+ padding: '11px 12px',
115
+ marginBottom: '14px',
116
+ },
117
+ };
118
+
119
+ function AuthCard({ kicker, title, subtitle, logoLetter = 'R', width = '438px', children }) {
120
+ const viewportWidth = window.useViewportWidth();
121
+ const isCompact = viewportWidth < 640;
122
+
123
+ return (
124
+ <div style={{ width, maxWidth: '100%', position: 'relative' }}>
125
+ <div
126
+ aria-hidden="true"
127
+ style={{
128
+ position: 'absolute',
129
+ inset: isCompact ? '16px -10px -12px 10px' : '18px -16px -16px 16px',
130
+ borderRadius: '30px',
131
+ background: 'linear-gradient(180deg, color-mix(in srgb, var(--brand-soft) 56%, transparent) 0%, transparent 82%)',
132
+ opacity: 0.9,
133
+ filter: 'blur(10px)',
134
+ pointerEvents: 'none',
135
+ }}
136
+ />
137
+ <div
138
+ style={{
139
+ position: 'relative',
140
+ background: 'linear-gradient(180deg, color-mix(in srgb, var(--surface-elevated) 94%, var(--surface-muted)) 0%, var(--surface-elevated) 100%)',
141
+ border: '1px solid color-mix(in srgb, var(--outline-strong) 82%, var(--border))',
142
+ borderRadius: '26px',
143
+ padding: isCompact ? '28px 22px' : '38px 38px 36px',
144
+ boxShadow: '0 24px 54px rgba(19,34,56,0.16)',
145
+ }}
146
+ >
147
+ <div
148
+ aria-hidden="true"
149
+ style={{
150
+ position: 'absolute',
151
+ left: '28px',
152
+ right: '28px',
153
+ top: 0,
154
+ height: '4px',
155
+ borderRadius: '0 0 999px 999px',
156
+ background: 'linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--brand) 72%, #fff) 50%, transparent 100%)',
157
+ opacity: 0.88,
158
+ }}
159
+ />
160
+ <div style={{ textAlign: 'center', marginBottom: '28px' }}>
161
+ {kicker ? (
162
+ <div
163
+ style={{
164
+ fontSize: '11px',
165
+ fontWeight: 700,
166
+ letterSpacing: '0.08em',
167
+ textTransform: 'uppercase',
168
+ color: 'var(--brand-strong)',
169
+ marginBottom: '12px',
170
+ }}
171
+ >
172
+ {kicker}
173
+ </div>
174
+ ) : null}
175
+ <div
176
+ style={{
177
+ width: '46px',
178
+ height: '46px',
179
+ borderRadius: '14px',
180
+ background: 'var(--brand)',
181
+ color: '#fff',
182
+ display: 'flex',
183
+ alignItems: 'center',
184
+ justifyContent: 'center',
185
+ fontSize: '22px',
186
+ fontWeight: 800,
187
+ margin: '0 auto 18px',
188
+ boxShadow: '0 14px 24px color-mix(in srgb, var(--brand) 20%, transparent)',
189
+ }}
190
+ >
191
+ {logoLetter}
192
+ </div>
193
+ <h1
194
+ style={{
195
+ fontSize: '20px',
196
+ fontWeight: 800,
197
+ color: 'var(--text-primary)',
198
+ margin: '0 0 8px',
199
+ letterSpacing: '-0.03em',
200
+ }}
201
+ >
202
+ {title}
203
+ </h1>
204
+ {subtitle ? (
205
+ <p
206
+ style={{
207
+ fontSize: '14px',
208
+ color: 'var(--text-secondary)',
209
+ margin: '0 auto',
210
+ maxWidth: '30ch',
211
+ lineHeight: 1.6,
212
+ }}
213
+ >
214
+ {subtitle}
215
+ </p>
216
+ ) : null}
217
+ </div>
218
+ {children}
219
+ </div>
220
+ </div>
221
+ );
222
+ }
223
+
224
+ function AuthLayout({ children }) {
225
+ const state = window.useAppState();
226
+ const { locale, setLocale, locales, getNativeLabel, t } = window.useI18n();
227
+ const viewportWidth = window.useViewportWidth();
228
+ const isStacked = viewportWidth < 960;
229
+ const config = state?.config || {};
230
+ const { History, LayoutGrid, ShieldCheck, Languages, ChevronDown } = lucide;
231
+ const [showLanguageMenu, setShowLanguageMenu] = React.useState(false);
232
+ const languageMenuRef = React.useRef(null);
233
+
234
+ React.useEffect(() => {
235
+ const handleMouseDown = (event) => {
236
+ if (languageMenuRef.current && !languageMenuRef.current.contains(event.target)) {
237
+ setShowLanguageMenu(false);
238
+ }
239
+ };
240
+ document.addEventListener('mousedown', handleMouseDown);
241
+ return () => document.removeEventListener('mousedown', handleMouseDown);
242
+ }, []);
243
+ const heroLineTwo = locale === 'zh-Hant'
244
+ ? { prefix: '資源', accent: '工作臺' }
245
+ : locale === 'en'
246
+ ? { prefix: 'Resource ', accent: 'Workspace' }
247
+ : locale === 'ja'
248
+ ? { prefix: 'リソース', accent: 'ワークスペース' }
249
+ : { prefix: '资源', accent: '工作台' };
250
+ const capabilityItems = [
251
+ {
252
+ title: '资源管理',
253
+ description: '公开与私有资源统一进入,常用内容不再分散。',
254
+ Icon: LayoutGrid,
255
+ },
256
+ {
257
+ title: '快速切换',
258
+ description: '收藏、最近访问与我创建的内容都能快速回到手边。',
259
+ Icon: History,
260
+ },
261
+ {
262
+ title: '后台配置',
263
+ description: '管理员可直接进入系统配置与邮件服务管理。',
264
+ Icon: ShieldCheck,
265
+ },
266
+ ];
267
+ const introSection = (
268
+ <div
269
+ style={{
270
+ display: 'grid',
271
+ gap: isStacked ? '14px' : '16px',
272
+ padding: isStacked ? '0' : '2px 0',
273
+ maxWidth: isStacked ? '560px' : '396px',
274
+ transform: isStacked ? 'none' : 'translateY(-28px)',
275
+ }}
276
+ >
277
+ <div style={{ display: 'grid', gap: '9px' }}>
278
+ <div style={{ display: 'grid', gap: '7px' }}>
279
+ <div style={{ fontSize: isStacked ? '30px' : '39px', lineHeight: 1.05, fontWeight: 900, color: 'var(--text-primary)', letterSpacing: '-0.05em' }}>
280
+ <span style={{ display: 'block' }}>{t('更顺手地进入你的')}</span>
281
+ <span style={{ display: 'block' }}>
282
+ {heroLineTwo.prefix}<span style={{ color: 'var(--brand-strong)' }}>{heroLineTwo.accent}</span>
283
+ </span>
284
+ </div>
285
+ <div style={{ fontSize: '15px', lineHeight: 1.7, color: 'var(--text-secondary)', maxWidth: '24ch' }}>
286
+ 统一入口与常用访问能力,从这里开始。
287
+ </div>
288
+ </div>
289
+ </div>
290
+
291
+ <div
292
+ style={{
293
+ display: 'grid',
294
+ gap: '14px',
295
+ padding: isStacked ? '18px 16px' : '18px',
296
+ borderRadius: '18px',
297
+ border: '1px solid color-mix(in srgb, var(--border) 80%, transparent)',
298
+ background: 'color-mix(in srgb, var(--surface-elevated) 82%, var(--bg-tertiary))',
299
+ boxShadow: '0 10px 20px color-mix(in srgb, var(--text-primary) 4%, transparent)',
300
+ }}
301
+ >
302
+ <div style={{ display: 'grid', gap: '6px' }}>
303
+ <div style={{ fontSize: '11px', fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--text-secondary)' }}>常用入口能力</div>
304
+ <strong style={{ fontSize: '15px', color: 'var(--text-primary)', letterSpacing: '-0.02em' }}>登录后可直接访问的核心能力</strong>
305
+ </div>
306
+
307
+ <div style={{ display: 'grid' }}>
308
+ {capabilityItems.map(({ title, description, Icon }, index) => (
309
+ <div
310
+ key={title}
311
+ style={{
312
+ display: 'grid',
313
+ gridTemplateColumns: 'auto 1fr',
314
+ gap: '12px',
315
+ alignItems: 'start',
316
+ padding: index === 0 ? '0 0 12px' : '12px 0',
317
+ borderTop: index === 0 ? 'none' : '1px solid color-mix(in srgb, var(--border) 72%, transparent)',
318
+ }}
319
+ >
320
+ <div
321
+ style={{
322
+ width: '34px',
323
+ height: '34px',
324
+ borderRadius: '10px',
325
+ background: 'color-mix(in srgb, var(--brand-soft) 64%, var(--surface-elevated))',
326
+ color: 'var(--brand-strong)',
327
+ display: 'flex',
328
+ alignItems: 'center',
329
+ justifyContent: 'center',
330
+ boxShadow: 'inset 0 0 0 1px color-mix(in srgb, var(--brand) 14%, transparent)',
331
+ flexShrink: 0,
332
+ }}
333
+ >
334
+ <Icon size={17} />
335
+ </div>
336
+
337
+ <div style={{ display: 'grid', gap: '3px' }}>
338
+ <strong style={{ fontSize: '14px', color: 'var(--text-primary)', letterSpacing: '-0.02em' }}>{title}</strong>
339
+ <span style={{ fontSize: '12px', color: 'var(--text-secondary)', lineHeight: 1.6 }}>
340
+ {description}
341
+ </span>
342
+ </div>
343
+ </div>
344
+ ))}
345
+ </div>
346
+ </div>
347
+ </div>
348
+ );
349
+ const formSection = (
350
+ <div
351
+ style={{
352
+ display: 'flex',
353
+ justifyContent: isStacked ? 'center' : 'flex-end',
354
+ width: '100%',
355
+ transform: isStacked ? 'none' : 'translateY(-10px)',
356
+ }}
357
+ >
358
+ {children}
359
+ </div>
360
+ );
361
+
362
+ return (
363
+ <div
364
+ style={{
365
+ minHeight: '100vh',
366
+ background: `
367
+ radial-gradient(circle at 74% 34%, color-mix(in srgb, var(--brand-soft) 72%, transparent) 0%, transparent 24%),
368
+ radial-gradient(circle at 18% 16%, color-mix(in srgb, var(--brand-soft) 42%, transparent) 0%, transparent 22%),
369
+ linear-gradient(135deg, color-mix(in srgb, var(--brand-soft) 20%, var(--bg-primary)) 0%, var(--bg-primary) 42%, color-mix(in srgb, var(--surface-tint) 12%, var(--bg-primary)) 100%),
370
+ var(--bg-primary)
371
+ `,
372
+ display: 'flex',
373
+ alignItems: 'center',
374
+ justifyContent: 'center',
375
+ padding: viewportWidth < 640 ? '20px 16px' : '32px 24px',
376
+ position: 'relative',
377
+ overflow: 'hidden',
378
+ }}
379
+ >
380
+ <div
381
+ aria-hidden="true"
382
+ style={{
383
+ position: 'absolute',
384
+ inset: 0,
385
+ backgroundImage: 'linear-gradient(color-mix(in srgb, var(--border) 24%, transparent) 1px, transparent 1px), linear-gradient(90deg, color-mix(in srgb, var(--border) 24%, transparent) 1px, transparent 1px)',
386
+ backgroundSize: '72px 72px',
387
+ opacity: 0.06,
388
+ pointerEvents: 'none',
389
+ }}
390
+ />
391
+ <div
392
+ ref={languageMenuRef}
393
+ style={{
394
+ position: 'absolute',
395
+ top: viewportWidth < 640 ? '14px' : '20px',
396
+ right: viewportWidth < 640 ? '14px' : '20px',
397
+ zIndex: 2,
398
+ }}
399
+ >
400
+ <button
401
+ data-rh-auth-language-trigger
402
+ type="button"
403
+ onClick={() => setShowLanguageMenu((value) => !value)}
404
+ style={{
405
+ minHeight: '38px',
406
+ padding: '0 12px',
407
+ borderRadius: '12px',
408
+ border: '1px solid color-mix(in srgb, var(--control-border) 72%, transparent)',
409
+ background: 'color-mix(in srgb, var(--surface-elevated) 92%, var(--surface-muted))',
410
+ color: 'var(--text-primary)',
411
+ cursor: 'pointer',
412
+ display: 'inline-flex',
413
+ alignItems: 'center',
414
+ gap: '8px',
415
+ fontSize: '13px',
416
+ fontWeight: 700,
417
+ boxShadow: 'var(--shadow-control)',
418
+ }}
419
+ >
420
+ <Languages size={15} />
421
+ <span>{getNativeLabel(locale)}</span>
422
+ <ChevronDown size={14} style={{ color: 'var(--text-secondary)' }} />
423
+ </button>
424
+ {showLanguageMenu && (
425
+ <div
426
+ style={{
427
+ position: 'absolute',
428
+ right: 0,
429
+ top: 'calc(100% + 6px)',
430
+ width: '168px',
431
+ background: 'color-mix(in srgb, var(--surface-elevated) 94%, var(--surface-muted))',
432
+ border: '1px solid color-mix(in srgb, var(--control-border) 72%, transparent)',
433
+ borderRadius: '12px',
434
+ boxShadow: 'var(--shadow-dropdown)',
435
+ padding: '4px',
436
+ }}
437
+ >
438
+ {locales.map((item) => (
439
+ <button
440
+ key={item}
441
+ data-rh-auth-language-option={item}
442
+ type="button"
443
+ onClick={() => { setLocale(item); setShowLanguageMenu(false); }}
444
+ style={{
445
+ display: 'flex',
446
+ alignItems: 'center',
447
+ width: '100%',
448
+ height: '36px',
449
+ padding: '0 10px',
450
+ border: 'none',
451
+ borderRadius: '8px',
452
+ cursor: 'pointer',
453
+ background: locale === item ? 'color-mix(in srgb, var(--brand-soft) 82%, var(--control-bg))' : 'transparent',
454
+ color: locale === item ? 'var(--brand-strong)' : 'var(--text-primary)',
455
+ fontSize: '14px',
456
+ fontWeight: locale === item ? 700 : 500,
457
+ }}
458
+ >
459
+ {getNativeLabel(item)}
460
+ </button>
461
+ ))}
462
+ </div>
463
+ )}
464
+ </div>
465
+ <div
466
+ aria-hidden="true"
467
+ style={{
468
+ position: 'absolute',
469
+ width: isStacked ? '360px' : '540px',
470
+ height: isStacked ? '360px' : '540px',
471
+ right: isStacked ? '50%' : '8%',
472
+ top: isStacked ? '20%' : '48%',
473
+ transform: isStacked ? 'translate(50%, -50%)' : 'translateY(-50%)',
474
+ background: 'radial-gradient(circle, color-mix(in srgb, var(--brand-soft) 68%, transparent) 0%, transparent 68%)',
475
+ opacity: 0.95,
476
+ pointerEvents: 'none',
477
+ }}
478
+ />
479
+
480
+ <div
481
+ style={{
482
+ width: '100%',
483
+ maxWidth: '1080px',
484
+ display: 'grid',
485
+ gridTemplateColumns: isStacked ? '1fr' : 'minmax(320px, 400px) minmax(420px, 500px)',
486
+ alignItems: 'center',
487
+ gap: viewportWidth < 640 ? '24px' : '38px',
488
+ position: 'relative',
489
+ zIndex: 1,
490
+ }}
491
+ >
492
+ {isStacked ? formSection : introSection}
493
+ {isStacked ? introSection : formSection}
494
+ </div>
495
+ <window.ToastContainer />
496
+ <window.EmailPreviewModal />
497
+ </div>
498
+ );
499
+ }
500
+
501
+ window.authStyles = authStyles;
502
+ window.AuthCard = AuthCard;
503
+ window.AuthLayout = AuthLayout;