signer-test-sdk-react 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +114 -0
  2. package/dist/src/AbstraxnProvider.d.ts +20 -0
  3. package/dist/src/AbstraxnProvider.js +2213 -0
  4. package/dist/src/AbstraxnProvider.js.map +1 -0
  5. package/dist/src/ConnectButton.css +217 -0
  6. package/dist/src/ConnectButton.d.ts +71 -0
  7. package/dist/src/ConnectButton.js +102 -0
  8. package/dist/src/ConnectButton.js.map +1 -0
  9. package/dist/src/ExternalWalletButtons.css +319 -0
  10. package/dist/src/ExternalWalletButtons.d.ts +56 -0
  11. package/dist/src/ExternalWalletButtons.js +245 -0
  12. package/dist/src/ExternalWalletButtons.js.map +1 -0
  13. package/dist/src/OnboardingUI.d.ts +63 -0
  14. package/dist/src/OnboardingUI.js +66 -0
  15. package/dist/src/OnboardingUI.js.map +1 -0
  16. package/dist/src/WalletModal.css +549 -0
  17. package/dist/src/WalletModal.d.ts +6 -0
  18. package/dist/src/WalletModal.js +89 -0
  19. package/dist/src/WalletModal.js.map +1 -0
  20. package/dist/src/components/OnboardingUI/OnboardingUI.css +727 -0
  21. package/dist/src/components/OnboardingUI/OnboardingUIReact.d.ts +15 -0
  22. package/dist/src/components/OnboardingUI/OnboardingUIReact.js +65 -0
  23. package/dist/src/components/OnboardingUI/OnboardingUIReact.js.map +1 -0
  24. package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +257 -0
  25. package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +3454 -0
  26. package/dist/src/components/OnboardingUI/OnboardingUIWeb.js.map +1 -0
  27. package/dist/src/components/OnboardingUI/components/EmailForm.d.ts +16 -0
  28. package/dist/src/components/OnboardingUI/components/EmailForm.js +19 -0
  29. package/dist/src/components/OnboardingUI/components/EmailForm.js.map +1 -0
  30. package/dist/src/components/OnboardingUI/components/Modal.d.ts +15 -0
  31. package/dist/src/components/OnboardingUI/components/Modal.js +68 -0
  32. package/dist/src/components/OnboardingUI/components/Modal.js.map +1 -0
  33. package/dist/src/components/OnboardingUI/components/OtpForm.d.ts +19 -0
  34. package/dist/src/components/OnboardingUI/components/OtpForm.js +58 -0
  35. package/dist/src/components/OnboardingUI/components/OtpForm.js.map +1 -0
  36. package/dist/src/components/OnboardingUI/components/PasskeyButton.d.ts +14 -0
  37. package/dist/src/components/OnboardingUI/components/PasskeyButton.js +22 -0
  38. package/dist/src/components/OnboardingUI/components/PasskeyButton.js.map +1 -0
  39. package/dist/src/components/OnboardingUI/components/SocialButtons.d.ts +15 -0
  40. package/dist/src/components/OnboardingUI/components/SocialButtons.js +15 -0
  41. package/dist/src/components/OnboardingUI/components/SocialButtons.js.map +1 -0
  42. package/dist/src/components/OnboardingUI/components/index.d.ts +13 -0
  43. package/dist/src/components/OnboardingUI/components/index.js +9 -0
  44. package/dist/src/components/OnboardingUI/components/index.js.map +1 -0
  45. package/dist/src/components/OnboardingUI/hooks/index.d.ts +7 -0
  46. package/dist/src/components/OnboardingUI/hooks/index.js +6 -0
  47. package/dist/src/components/OnboardingUI/hooks/index.js.map +1 -0
  48. package/dist/src/components/OnboardingUI/hooks/useAuthMethods.d.ts +11 -0
  49. package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js +146 -0
  50. package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js.map +1 -0
  51. package/dist/src/components/OnboardingUI/hooks/useOnboarding.d.ts +21 -0
  52. package/dist/src/components/OnboardingUI/hooks/useOnboarding.js +135 -0
  53. package/dist/src/components/OnboardingUI/hooks/useOnboarding.js.map +1 -0
  54. package/dist/src/components/OnboardingUI/index.d.ts +12 -0
  55. package/dist/src/components/OnboardingUI/index.js +15 -0
  56. package/dist/src/components/OnboardingUI/index.js.map +1 -0
  57. package/dist/src/hooks.d.ts +204 -0
  58. package/dist/src/hooks.js +394 -0
  59. package/dist/src/hooks.js.map +1 -0
  60. package/dist/src/index.d.ts +14 -0
  61. package/dist/src/index.js +11 -0
  62. package/dist/src/index.js.map +1 -0
  63. package/dist/src/types.d.ts +181 -0
  64. package/dist/src/types.js +2 -0
  65. package/dist/src/types.js.map +1 -0
  66. package/dist/src/wagmiConfig.d.ts +147 -0
  67. package/dist/src/wagmiConfig.js +81 -0
  68. package/dist/src/wagmiConfig.js.map +1 -0
  69. package/dist/tsconfig.tsbuildinfo +1 -0
  70. package/package.json +68 -0
@@ -0,0 +1,3454 @@
1
+ // CSS styles embedded as string
2
+ const ONBOARDING_UI_STYLES = `
3
+ .onboarding-container {
4
+ display: flex;
5
+ flex-direction: column;
6
+ align-items: center;
7
+ justify-content: center;
8
+ min-height: 100vh;
9
+ padding: 20px;
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
11
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
12
+ sans-serif;
13
+ -webkit-font-smoothing: antialiased;
14
+ -moz-osx-font-smoothing: grayscale;
15
+ }
16
+
17
+ /* Inline mode - no extra height */
18
+ .onboarding-container.onboarding-inline {
19
+ min-height: auto;
20
+ padding: 0;
21
+ }
22
+
23
+ /* Modal Styles */
24
+ .onboarding-modal-overlay {
25
+ position: fixed;
26
+ top: 0;
27
+ left: 0;
28
+ right: 0;
29
+ bottom: 0;
30
+ background-color: rgba(0, 0, 0, 0.6);
31
+ backdrop-filter: blur(8px);
32
+ -webkit-backdrop-filter: blur(8px);
33
+ display: none; /* Start hidden by default to prevent flicker */
34
+ align-items: center;
35
+ justify-content: center;
36
+ z-index: 9999;
37
+ opacity: 0;
38
+ transition: opacity 0.3s ease-in-out;
39
+ padding: 16px;
40
+ overflow: hidden;
41
+ overscroll-behavior: contain;
42
+ }
43
+
44
+ .onboarding-modal-overlay::-webkit-scrollbar {
45
+ display: none;
46
+ }
47
+
48
+ .onboarding-modal-overlay {
49
+ -ms-overflow-style: none;
50
+ scrollbar-width: none;
51
+ }
52
+
53
+ .onboarding-modal-overlay.onboarding-modal-open {
54
+ display: flex; /* Show when explicitly opened */
55
+ opacity: 1;
56
+ }
57
+
58
+ .onboarding-modal-overlay.onboarding-modal-closing {
59
+ opacity: 0;
60
+ }
61
+
62
+ .onboarding-modal-content {
63
+ position: relative;
64
+ width: 420px;
65
+ max-width: 420px;
66
+ min-width: 420px;
67
+ max-height: calc(100vh - 32px);
68
+ overflow: hidden;
69
+ transform: scale(0.95);
70
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), height 0.3s ease-in-out;
71
+ display: flex;
72
+ flex-direction: column;
73
+ box-sizing: border-box;
74
+ }
75
+
76
+ @media (max-width: 480px) {
77
+ .onboarding-modal-content {
78
+ width: 100%;
79
+ max-width: 100%;
80
+ min-width: 100%;
81
+ max-height: 100vh;
82
+ border-radius: 0;
83
+ }
84
+
85
+ .onboarding-card {
86
+ width: 100%;
87
+ max-width: 100%;
88
+ min-width: 100%;
89
+ }
90
+
91
+ .onboarding-modal-overlay {
92
+ padding: 0;
93
+ }
94
+ }
95
+
96
+ .onboarding-modal-overlay.onboarding-modal-open .onboarding-modal-content {
97
+ transform: scale(1);
98
+ }
99
+
100
+ .onboarding-modal-overlay.onboarding-modal-closing .onboarding-modal-content {
101
+ transform: scale(0.95);
102
+ }
103
+
104
+ .onboarding-modal-close {
105
+ position: absolute;
106
+ top: 24px;
107
+ right: 24px;
108
+ width: 32px;
109
+ height: 32px;
110
+ border: none;
111
+ background-color: #f3f4f6;
112
+ border-radius: 8px;
113
+ cursor: pointer;
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ font-size: 18px;
118
+ line-height: 1;
119
+ color: #6b7280;
120
+ transition: all 0.2s ease;
121
+ z-index: 10;
122
+ padding: 0;
123
+ font-weight: 400;
124
+ }
125
+
126
+ .onboarding-modal-close:hover {
127
+ background-color: #e5e7eb;
128
+ color: #374151;
129
+ transform: scale(1.05);
130
+ }
131
+
132
+ .onboarding-theme-dark .onboarding-modal-close {
133
+ background-color: #374151;
134
+ color: #9ca3af;
135
+ }
136
+
137
+ .onboarding-theme-dark .onboarding-modal-close:hover {
138
+ background-color: #4b5563;
139
+ color: #ffffff;
140
+ }
141
+
142
+ /* Modal container adjustments */
143
+ .onboarding-modal-content .onboarding-container {
144
+ min-height: auto;
145
+ padding: 0;
146
+ }
147
+
148
+ /* Light Theme */
149
+ .onboarding-theme-light {
150
+ color: #000000;
151
+ }
152
+
153
+ /* Dark Theme */
154
+ .onboarding-theme-dark {
155
+ color: #ffffff;
156
+ }
157
+
158
+ .onboarding-card {
159
+ width: 420px;
160
+ max-width: 420px;
161
+ min-width: 420px;
162
+ padding: 40px;
163
+ border-radius: 16px;
164
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
165
+ background-color: inherit;
166
+ position: relative;
167
+ overflow-y: auto;
168
+ overflow-x: hidden;
169
+ max-height: calc(100vh - 32px);
170
+ overscroll-behavior: contain;
171
+ transition: height 0.3s ease-in-out;
172
+ box-sizing: border-box;
173
+ }
174
+
175
+ .onboarding-card::-webkit-scrollbar {
176
+ width: 6px;
177
+ }
178
+
179
+ .onboarding-card::-webkit-scrollbar-track {
180
+ background: transparent;
181
+ }
182
+
183
+ .onboarding-card::-webkit-scrollbar-thumb {
184
+ background: rgba(0, 0, 0, 0.2);
185
+ border-radius: 3px;
186
+ }
187
+
188
+ .onboarding-theme-dark .onboarding-card::-webkit-scrollbar-thumb {
189
+ background: rgba(255, 255, 255, 0.2);
190
+ }
191
+
192
+ @media (max-width: 480px) {
193
+ .onboarding-card {
194
+ padding: 32px 24px;
195
+ border-radius: 0;
196
+ max-height: 100vh;
197
+ }
198
+ }
199
+
200
+ .onboarding-theme-light .onboarding-card {
201
+ background-color: #ffffff;
202
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
203
+ border: 1px solid rgba(0, 0, 0, 0.06);
204
+ }
205
+
206
+ .onboarding-theme-dark .onboarding-card {
207
+ background-color: #1f2937;
208
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 0 1px rgba(255, 255, 255, 0.1);
209
+ border: 1px solid rgba(255, 255, 255, 0.1);
210
+ }
211
+
212
+ .onboarding-header {
213
+ text-align: center;
214
+ margin-bottom: 32px;
215
+ }
216
+
217
+ .onboarding-logo-section {
218
+ display: flex;
219
+ flex-direction: column;
220
+ align-items: center;
221
+ margin-bottom: 24px;
222
+ }
223
+
224
+ .onboarding-logo-container {
225
+ display: flex;
226
+ justify-content: center;
227
+ align-items: center;
228
+ margin-bottom: 6px;
229
+ gap: 10px;
230
+ }
231
+
232
+ .onboarding-logo-img {
233
+ max-width: 120px;
234
+ max-height: 60px;
235
+ object-fit: contain;
236
+ }
237
+
238
+ .onboarding-title {
239
+ font-size: 24px;
240
+ font-weight: 600;
241
+ margin: 0;
242
+ color: inherit;
243
+ text-align: center;
244
+ margin-bottom: 28px;
245
+ letter-spacing: -0.01em;
246
+ line-height: 1.4;
247
+ }
248
+
249
+ @media (max-width: 480px) {
250
+ .onboarding-title {
251
+ font-size: 24px;
252
+ margin-bottom: 24px;
253
+ }
254
+ }
255
+
256
+ .onboarding-theme-light .onboarding-title {
257
+ color: #111827;
258
+ }
259
+
260
+ .onboarding-theme-dark .onboarding-title {
261
+ color: #ffffff;
262
+ }
263
+
264
+ .onboarding-form {
265
+ width: 100%;
266
+ }
267
+
268
+ .onboarding-input-group {
269
+ margin-bottom: 20px;
270
+ }
271
+
272
+ .onboarding-input-label {
273
+ display: block;
274
+ font-size: 14px;
275
+ font-weight: 500;
276
+ color: #374151;
277
+ margin-bottom: 8px;
278
+ letter-spacing: -0.01em;
279
+ line-height: 1.5;
280
+ text-align: left;
281
+ width: 100%;
282
+ }
283
+
284
+ .onboarding-theme-dark .onboarding-input-label {
285
+ color: #e5e7eb;
286
+ }
287
+
288
+ .onboarding-input-wrapper {
289
+ position: relative;
290
+ display: flex;
291
+ align-items: center;
292
+ }
293
+
294
+ .onboarding-input-icon {
295
+ position: absolute;
296
+ left: 14px;
297
+ color: #9ca3af;
298
+ pointer-events: none;
299
+ z-index: 1;
300
+ width: 18px;
301
+ height: 18px;
302
+ }
303
+
304
+ .onboarding-theme-dark .onboarding-input-icon {
305
+ color: #9ca3af;
306
+ }
307
+
308
+ .onboarding-input {
309
+ width: 100%;
310
+ padding: 10px 14px 10px 44px;
311
+ font-size: 14px;
312
+ border: 1.5px solid #e5e7eb;
313
+ border-radius: 8px;
314
+ background-color: inherit;
315
+ transition: all 0.15s ease;
316
+ box-sizing: border-box;
317
+ font-weight: 400;
318
+ letter-spacing: 0;
319
+ line-height: 1.5;
320
+ }
321
+
322
+ .onboarding-theme-light .onboarding-input {
323
+ background-color: #ffffff;
324
+ border-color: #e5e7eb;
325
+ color: #000000;
326
+ }
327
+
328
+ .onboarding-theme-dark .onboarding-input {
329
+ background-color: #374151;
330
+ color: #ffffff;
331
+ }
332
+
333
+ .onboarding-input:focus {
334
+ outline: none;
335
+ }
336
+
337
+ .onboarding-theme-light .onboarding-input:focus {
338
+ border-color: #111827;
339
+ box-shadow: 0 0 0 3px rgba(17, 24, 39, 0.1);
340
+ background-color: #ffffff;
341
+ color: #000000;
342
+ }
343
+
344
+ .onboarding-theme-dark .onboarding-input {
345
+ border-color: #4b5563;
346
+ background-color: #374151;
347
+ }
348
+
349
+ .onboarding-theme-dark .onboarding-input:focus {
350
+ border-color: #9ca3af;
351
+ box-shadow: 0 0 0 3px rgba(156, 163, 175, 0.2);
352
+ background-color: #4b5563;
353
+ color: #ffffff;
354
+ }
355
+
356
+ .onboarding-input:disabled {
357
+ opacity: 0.6;
358
+ cursor: not-allowed;
359
+ }
360
+
361
+ /* Arrow button inside email input */
362
+ .onboarding-input-arrow-button {
363
+ position: absolute;
364
+ right: 8px;
365
+ top: 50%;
366
+ transform: translateY(-50%);
367
+ background: none;
368
+ border: none;
369
+ cursor: pointer;
370
+ padding: 4px;
371
+ display: flex;
372
+ align-items: center;
373
+ justify-content: center;
374
+ z-index: 2;
375
+ transition: all 0.2s ease;
376
+ border-radius: 4px;
377
+ }
378
+
379
+ /* Light theme arrow button */
380
+ .onboarding-theme-light .onboarding-input-arrow-button {
381
+ color: #111827;
382
+ }
383
+
384
+ .onboarding-theme-light .onboarding-input-arrow-button:hover {
385
+ background-color: rgba(17, 24, 39, 0.1);
386
+ color: #1f2937;
387
+ }
388
+
389
+ .onboarding-theme-light .onboarding-input-arrow-button:active {
390
+ background-color: rgba(17, 24, 39, 0.15);
391
+ }
392
+
393
+ /* Dark theme arrow button */
394
+ .onboarding-theme-dark .onboarding-input-arrow-button {
395
+ color: #9ca3af;
396
+ }
397
+
398
+ .onboarding-theme-dark .onboarding-input-arrow-button:hover {
399
+ background-color: rgba(156, 163, 175, 0.2);
400
+ color: #d1d5db;
401
+ }
402
+
403
+ .onboarding-theme-dark .onboarding-input-arrow-button:active {
404
+ background-color: rgba(156, 163, 175, 0.25);
405
+ }
406
+
407
+ .onboarding-input-arrow-button:disabled {
408
+ opacity: 0.5;
409
+ cursor: not-allowed;
410
+ }
411
+
412
+ .onboarding-input-arrow-icon {
413
+ width: 20px;
414
+ height: 20px;
415
+ }
416
+
417
+ .onboarding-error {
418
+ padding: 12px;
419
+ margin-bottom: 16px;
420
+ background-color: #fee2e2;
421
+ color: #dc2626;
422
+ border-radius: 8px;
423
+ font-size: 14px;
424
+ }
425
+
426
+ .onboarding-theme-dark .onboarding-error {
427
+ background-color: #7f1d1d;
428
+ color: #fca5a5;
429
+ }
430
+
431
+ .onboarding-button {
432
+ width: 100%;
433
+ padding: 13px 24px;
434
+ font-size: 15px;
435
+ font-weight: 600;
436
+ border: none;
437
+ border-radius: 12px;
438
+ cursor: pointer;
439
+ transition: all 0.2s ease;
440
+ display: flex;
441
+ align-items: center;
442
+ justify-content: center;
443
+ gap: 8px;
444
+ margin-bottom: 12px;
445
+ letter-spacing: -0.01em;
446
+ line-height: 1.5;
447
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
448
+ min-height: 48px;
449
+ }
450
+
451
+ .onboarding-button:disabled {
452
+ opacity: 0.6;
453
+ cursor: not-allowed;
454
+ }
455
+
456
+ .onboarding-button-primary {
457
+ font-weight: 600;
458
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
459
+ }
460
+
461
+ /* Light theme button */
462
+ .onboarding-theme-light .onboarding-button-primary {
463
+ background-color: #111827;
464
+ color: #ffffff;
465
+ }
466
+
467
+ .onboarding-theme-light .onboarding-button-primary:hover:not(:disabled) {
468
+ background-color: #1f2937;
469
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
470
+ transform: translateY(-1px);
471
+ }
472
+
473
+ .onboarding-theme-light .onboarding-button-primary:active:not(:disabled) {
474
+ background-color: #374151;
475
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
476
+ transform: translateY(0);
477
+ }
478
+
479
+ /* Dark theme button */
480
+ .onboarding-theme-dark .onboarding-button-primary {
481
+ background-color: #4b5563;
482
+ color: #ffffff;
483
+ }
484
+
485
+ .onboarding-theme-dark .onboarding-button-primary:hover:not(:disabled) {
486
+ background-color: #6b7280;
487
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
488
+ transform: translateY(-1px);
489
+ }
490
+
491
+ .onboarding-theme-dark .onboarding-button-primary:active:not(:disabled) {
492
+ background-color: #6b7280;
493
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
494
+ transform: translateY(0);
495
+ }
496
+
497
+ .onboarding-button-google {
498
+ background-color: #ffffff;
499
+ color: #1f2937;
500
+ border: 1.5px solid #e5e7eb;
501
+ font-weight: 500;
502
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
503
+ font-size: 14px;
504
+ }
505
+
506
+ .onboarding-button-social {
507
+ background-color: #ffffff;
508
+ color: #1f2937;
509
+ border: 1.5px solid #e5e7eb;
510
+ font-weight: 500;
511
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
512
+ font-size: 14px;
513
+ }
514
+
515
+ .onboarding-theme-dark .onboarding-button-google {
516
+ background-color: #374151;
517
+ color: #ffffff;
518
+ border-color: #4b5563;
519
+ }
520
+
521
+ .onboarding-theme-dark .onboarding-button-social {
522
+ background-color: #2f3239;
523
+ color: #ffffff;
524
+ border-color: #4b5563;
525
+ }
526
+
527
+ .onboarding-button-google:hover:not(:disabled) {
528
+ background-color: #f9fafb;
529
+ border-color: #d1d5db;
530
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
531
+ transform: translateY(-1px);
532
+ }
533
+
534
+ .onboarding-button-social:hover:not(:disabled) {
535
+ background-color: #f9fafb;
536
+ border-color: #d1d5db;
537
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
538
+ transform: translateY(-1px);
539
+ }
540
+
541
+ .onboarding-theme-dark .onboarding-button-google:hover:not(:disabled) {
542
+ background-color: #4b5563;
543
+ border-color: #6b7280;
544
+ }
545
+
546
+ .onboarding-theme-dark .onboarding-button-social:hover:not(:disabled) {
547
+ background-color: #3c404a;
548
+ border-color: #6b7280;
549
+ }
550
+
551
+ .onboarding-button-social-icon {
552
+ width: 100%;
553
+ height: 48px;
554
+ display: inline-flex;
555
+ align-items: center;
556
+ justify-content: center;
557
+ border: 1.5px solid #e5e7eb;
558
+ border-radius: 12px;
559
+ background-color: #ffffff;
560
+ color: #1f2937;
561
+ cursor: pointer;
562
+ transition: all 0.2s ease;
563
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
564
+ flex: 1;
565
+ }
566
+
567
+ .onboarding-button-social-icon:hover:not(:disabled) {
568
+ background-color: #f9fafb;
569
+ border-color: #d1d5db;
570
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
571
+ transform: translateY(-1px);
572
+ }
573
+
574
+ .onboarding-theme-dark .onboarding-button-social-icon {
575
+ background-color: #2f3239;
576
+ border-color: #4b5563;
577
+ color: #ffffff;
578
+ }
579
+
580
+ .onboarding-theme-dark .onboarding-button-social-icon:hover:not(:disabled) {
581
+ background-color: #3c404a;
582
+ border-color: #6b7280;
583
+ }
584
+
585
+ .onboarding-social-grid {
586
+ display: flex;
587
+ gap: 12px;
588
+ flex-wrap: nowrap;
589
+ margin-top: 8px;
590
+ width: 100%;
591
+ }
592
+
593
+ .onboarding-social-icon {
594
+ width: 20px;
595
+ height: 20px;
596
+ }
597
+
598
+ .onboarding-google-icon {
599
+ width: 20px;
600
+ height: 20px;
601
+ }
602
+
603
+ .onboarding-divider {
604
+ display: flex;
605
+ align-items: center;
606
+ margin: 20px 0;
607
+ position: relative;
608
+ }
609
+
610
+ .onboarding-divider::before,
611
+ .onboarding-divider::after {
612
+ content: '';
613
+ flex: 1;
614
+ height: 1px;
615
+ background-color: #e5e7eb;
616
+ }
617
+
618
+ .onboarding-theme-dark .onboarding-divider::before,
619
+ .onboarding-theme-dark .onboarding-divider::after {
620
+ background-color: #4b5563;
621
+ }
622
+
623
+ .onboarding-divider-text {
624
+ padding: 0 16px;
625
+ font-size: 14px;
626
+ color: #6b7280;
627
+ }
628
+
629
+ .onboarding-theme-dark .onboarding-divider-text {
630
+ color: #9ca3af;
631
+ }
632
+
633
+
634
+ .onboarding-footer {
635
+ margin-top: 16px;
636
+ padding: 0;
637
+ text-align: center;
638
+ background: transparent;
639
+ border: none;
640
+ border-radius: 0;
641
+ }
642
+
643
+ .onboarding-footer-text {
644
+ font-size: 11px;
645
+ color: #9ca3af;
646
+ margin: 0;
647
+ line-height: 1.5;
648
+ font-weight: 400;
649
+ letter-spacing: 0.01em;
650
+ }
651
+
652
+ .onboarding-theme-dark .onboarding-footer-text {
653
+ color: #6b7280;
654
+ }
655
+
656
+ .onboarding-footer-brand {
657
+ font-weight: 700;
658
+ text-decoration: none;
659
+ margin-left: 2px;
660
+ cursor: pointer;
661
+ transition: opacity 0.15s ease;
662
+ }
663
+
664
+ .onboarding-theme-light .onboarding-footer-brand {
665
+ color: #111827;
666
+ }
667
+
668
+ .onboarding-theme-dark .onboarding-footer-brand {
669
+ color: #9ca3af;
670
+ }
671
+
672
+ .onboarding-footer-brand:hover {
673
+ text-decoration: none;
674
+ opacity: 0.8;
675
+ }
676
+
677
+ /* Modal container adjustments */
678
+ .onboarding-modal-content .onboarding-container {
679
+ min-height: auto;
680
+ padding: 0;
681
+ }
682
+
683
+ .onboarding-modal-content .onboarding-card {
684
+ margin: 0;
685
+ }
686
+
687
+ /* OTP Verification Screen */
688
+ .onboarding-otp-verification {
689
+ display: flex;
690
+ flex-direction: column;
691
+ align-items: center;
692
+ text-align: center;
693
+ width: 100%;
694
+ }
695
+
696
+ .onboarding-otp-icon-container {
697
+ width: 72px;
698
+ height: 72px;
699
+ border-radius: 50%;
700
+ display: flex;
701
+ align-items: center;
702
+ justify-content: center;
703
+ margin-bottom: 20px;
704
+ position: relative;
705
+ }
706
+
707
+ .onboarding-theme-light .onboarding-otp-icon-container {
708
+ background: linear-gradient(135deg, rgba(17, 24, 39, 0.1) 0%, rgba(17, 24, 39, 0.05) 100%);
709
+ }
710
+
711
+ .onboarding-theme-dark .onboarding-otp-icon-container {
712
+ background: linear-gradient(135deg, rgba(156, 163, 175, 0.2) 0%, rgba(156, 163, 175, 0.1) 100%);
713
+ }
714
+
715
+ .onboarding-otp-icon-inner {
716
+ width: 48px;
717
+ height: 48px;
718
+ background-color: #dc2626;
719
+ border-radius: 10px;
720
+ display: flex;
721
+ align-items: center;
722
+ justify-content: center;
723
+ }
724
+
725
+ .onboarding-otp-icon {
726
+ width: 24px;
727
+ height: 24px;
728
+ color: #ffffff;
729
+ }
730
+
731
+ .onboarding-otp-title {
732
+ font-size: 22px;
733
+ font-weight: 600;
734
+ margin: 0 0 12px 0;
735
+ color: inherit;
736
+ line-height: 1.4;
737
+ letter-spacing: -0.01em;
738
+ }
739
+
740
+ .onboarding-theme-light .onboarding-otp-title {
741
+ color: #111827;
742
+ }
743
+
744
+ .onboarding-theme-dark .onboarding-otp-title {
745
+ color: #ffffff;
746
+ }
747
+
748
+ .onboarding-otp-instruction {
749
+ font-size: 14px;
750
+ color: #6b7280;
751
+ margin: 0 0 4px 0;
752
+ line-height: 1.5;
753
+ font-weight: 400;
754
+ }
755
+
756
+ .onboarding-theme-dark .onboarding-otp-instruction {
757
+ color: #9ca3af;
758
+ }
759
+
760
+ .onboarding-otp-email {
761
+ font-size: 14px;
762
+ font-weight: 600;
763
+ color: inherit;
764
+ margin: 0 0 24px 0;
765
+ line-height: 1.5;
766
+ }
767
+
768
+ .onboarding-theme-light .onboarding-otp-email {
769
+ color: #111827;
770
+ }
771
+
772
+ .onboarding-theme-dark .onboarding-otp-email {
773
+ color: #ffffff;
774
+ }
775
+
776
+ .onboarding-otp-inputs-container {
777
+ display: flex;
778
+ gap: 10px;
779
+ justify-content: center;
780
+ margin-bottom: 24px;
781
+ width: 100%;
782
+ }
783
+
784
+ .onboarding-otp-input {
785
+ width: 44px;
786
+ height: 52px;
787
+ text-align: center;
788
+ font-size: 20px;
789
+ font-weight: 600;
790
+ border: 1.5px solid #e5e7eb;
791
+ border-radius: 8px;
792
+ background-color: inherit;
793
+ transition: all 0.15s ease;
794
+ box-sizing: border-box;
795
+ line-height: 1;
796
+ }
797
+
798
+ .onboarding-theme-light .onboarding-otp-input {
799
+ background-color: #ffffff;
800
+ border-color: #e5e7eb;
801
+ color: #000000;
802
+ }
803
+
804
+ .onboarding-otp-input:focus {
805
+ outline: none;
806
+ }
807
+
808
+ .onboarding-theme-light .onboarding-otp-input:focus {
809
+ border-color: #111827;
810
+ box-shadow: 0 0 0 3px rgba(17, 24, 39, 0.1);
811
+ background-color: #ffffff;
812
+ color: #000000;
813
+ }
814
+
815
+ .onboarding-theme-dark .onboarding-otp-input {
816
+ border-color: #4b5563;
817
+ background-color: #374151;
818
+ color: #ffffff;
819
+ }
820
+
821
+ .onboarding-theme-dark .onboarding-otp-input:focus {
822
+ border-color: #9ca3af;
823
+ box-shadow: 0 0 0 3px rgba(156, 163, 175, 0.2);
824
+ background-color: #4b5563;
825
+ color: #ffffff;
826
+ }
827
+
828
+ .onboarding-otp-resend {
829
+ text-align: center;
830
+ margin-top: 20px;
831
+ line-height: 1.5;
832
+ }
833
+
834
+ .onboarding-otp-resend-text {
835
+ font-size: 13px;
836
+ color: #6b7280;
837
+ margin: 0;
838
+ line-height: 1.5;
839
+ font-weight: 400;
840
+ }
841
+
842
+ .onboarding-theme-dark .onboarding-otp-resend-text {
843
+ color: #9ca3af;
844
+ }
845
+
846
+ .onboarding-otp-resend-link {
847
+ color: #dc2626;
848
+ text-decoration: underline;
849
+ cursor: pointer;
850
+ font-weight: 500;
851
+ transition: color 0.15s ease;
852
+ font-size: 13px;
853
+ }
854
+
855
+ .onboarding-otp-resend-link:hover:not(:disabled) {
856
+ color: #b91c1c;
857
+ }
858
+
859
+ .onboarding-otp-resend-link:disabled {
860
+ color: #9ca3af;
861
+ cursor: not-allowed;
862
+ text-decoration: none;
863
+ }
864
+
865
+ .onboarding-theme-dark .onboarding-otp-resend-link:disabled {
866
+ color: #6b7280;
867
+ }
868
+
869
+ /* Passkey Button Styles */
870
+ .onboarding-button-passkey {
871
+ background-color: #374151;
872
+ color: #ffffff;
873
+ border: 1.5px solid #4b5563;
874
+ font-weight: 500;
875
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
876
+ font-size: 14px;
877
+ }
878
+
879
+ .onboarding-theme-dark .onboarding-button-passkey {
880
+ background-color: #4b5563;
881
+ color: #ffffff;
882
+ border-color: #6b7280;
883
+ }
884
+
885
+ .onboarding-button-passkey:hover:not(:disabled) {
886
+ background-color: #4b5563;
887
+ border-color: #6b7280;
888
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
889
+ transform: translateY(-1px);
890
+ }
891
+
892
+ .onboarding-theme-dark .onboarding-button-passkey:hover:not(:disabled) {
893
+ background-color: #6b7280;
894
+ border-color: #9ca3af;
895
+ }
896
+
897
+ .onboarding-passkey-icon {
898
+ width: 20px;
899
+ height: 20px;
900
+ }
901
+
902
+ .onboarding-passkey-signup-link {
903
+ text-decoration: none;
904
+ cursor: pointer;
905
+ font-size: 14px;
906
+ font-weight: 500;
907
+ margin-top: 12px;
908
+ display: inline-block;
909
+ transition: color 0.15s ease;
910
+ }
911
+
912
+ .onboarding-theme-light .onboarding-passkey-signup-link {
913
+ color: #111827;
914
+ }
915
+
916
+ .onboarding-theme-light .onboarding-passkey-signup-link:hover {
917
+ color: #1f2937;
918
+ text-decoration: underline;
919
+ }
920
+
921
+ .onboarding-theme-dark .onboarding-passkey-signup-link {
922
+ color: #9ca3af;
923
+ }
924
+
925
+ .onboarding-theme-dark .onboarding-passkey-signup-link:hover {
926
+ color: #d1d5db;
927
+ text-decoration: underline;
928
+ }
929
+ `;
930
+ /**
931
+ * OnboardingUI Class
932
+ *
933
+ * A customizable user onboarding component with email-OTP and Google login options.
934
+ * Works with vanilla JavaScript - no framework dependencies required.
935
+ */
936
+ export class OnboardingUIWeb {
937
+ static stylesInjected = false;
938
+ config;
939
+ container = null;
940
+ rootElement = null;
941
+ modalOverlay = null;
942
+ modalContent = null;
943
+ emailInput = null;
944
+ otpInput = null;
945
+ otpInputs = [];
946
+ emailForm = null;
947
+ continueButton = null;
948
+ emailArrowButton = null;
949
+ googleButton = null;
950
+ twitterButton = null;
951
+ discordButton = null;
952
+ passkeyLoginButton = null;
953
+ passkeySignupLink = null;
954
+ passkeyDivider = null;
955
+ passkeyErrorElement = null;
956
+ errorElement = null;
957
+ otpGroup = null;
958
+ otpVerificationScreen = null;
959
+ closeButton = null;
960
+ resendButton = null;
961
+ socialGrid = null;
962
+ externalWalletContainer = null;
963
+ externalWalletDivider = null;
964
+ divider = null;
965
+ footer = null;
966
+ verifyButton = null;
967
+ email = '';
968
+ otp = '';
969
+ otpSent = false;
970
+ loading = false;
971
+ activeButton = null;
972
+ resendCooldown = 0;
973
+ externalWalletsEnabled = false;
974
+ constructor(config, externalWalletsEnabled = false) {
975
+ this.externalWalletsEnabled = externalWalletsEnabled;
976
+ const defaultTheme = config.theme || 'light';
977
+ this.config = {
978
+ theme: defaultTheme,
979
+ showFooter: config.showFooter !== false,
980
+ className: config.className || '',
981
+ modal: config.modal !== false,
982
+ closeOnBackdropClick: config.closeOnBackdropClick !== false,
983
+ showCloseButton: config.showCloseButton !== false,
984
+ onboardTitle: config.onboardTitle || 'Sign in',
985
+ handleUrlParams: config.handleUrlParams !== false,
986
+ ...config,
987
+ };
988
+ }
989
+ /**
990
+ * Show external wallet container and divider
991
+ * Call this before init() to prevent flicker when external wallets are enabled
992
+ */
993
+ showExternalWallets() {
994
+ if (this.externalWalletContainer) {
995
+ this.externalWalletContainer.style.display = '';
996
+ }
997
+ if (this.externalWalletDivider) {
998
+ this.externalWalletDivider.style.display = '';
999
+ }
1000
+ }
1001
+ /**
1002
+ * Initialize and render the component
1003
+ */
1004
+ init() {
1005
+ this.injectStyles();
1006
+ this.setupContainer();
1007
+ this.render();
1008
+ this.attachEventListeners();
1009
+ this.checkUrlParams();
1010
+ }
1011
+ /**
1012
+ * Check URL parameters for social login callbacks
1013
+ */
1014
+ checkUrlParams() {
1015
+ if (typeof window === 'undefined')
1016
+ return;
1017
+ if (!this.config.handleUrlParams)
1018
+ return;
1019
+ const params = new URLSearchParams(window.location.search);
1020
+ const success = params.get('success');
1021
+ const error = params.get('error');
1022
+ // If success=true, always show loading (less restrictive)
1023
+ if (success === 'true') {
1024
+ // Ensure modal is open if in modal mode
1025
+ if (this.config.modal && this.modalOverlay) {
1026
+ this.modalOverlay.classList.remove('onboarding-modal-closing');
1027
+ this.modalOverlay.classList.add('onboarding-modal-open');
1028
+ this.modalOverlay.style.display = 'flex';
1029
+ document.body.style.overflow = 'hidden';
1030
+ }
1031
+ this.showLoadingScreen();
1032
+ return;
1033
+ }
1034
+ // Check login source to decide if we should handle this (for errors)
1035
+ const source = localStorage.getItem('abstraxn_login_source');
1036
+ const isModal = this.config.modal;
1037
+ if (source) {
1038
+ if (source === 'modal' && !isModal)
1039
+ return; // Modal initiated, I am Inline -> Ignore
1040
+ if (source === 'inline' && isModal)
1041
+ return; // Inline initiated, I am Modal -> Ignore
1042
+ }
1043
+ else {
1044
+ // No source (e.g. unknown origin), default to Modal handling it
1045
+ if (!isModal)
1046
+ return;
1047
+ }
1048
+ if (error) {
1049
+ this.showError(decodeURIComponent(error));
1050
+ }
1051
+ }
1052
+ /**
1053
+ * Show error screen or modal (public method)
1054
+ */
1055
+ showError(message) {
1056
+ // Hide loading modal if it exists
1057
+ this.hideLoadingModal();
1058
+ if (this.config.modal) {
1059
+ this.showErrorModal(message);
1060
+ }
1061
+ else {
1062
+ this.showErrorScreen(message);
1063
+ }
1064
+ }
1065
+ /**
1066
+ * Show error screen
1067
+ */
1068
+ showErrorScreen(message) {
1069
+ if (!this.rootElement)
1070
+ return;
1071
+ // Hide loading modal if it exists (for inline components)
1072
+ this.hideLoadingModal();
1073
+ const card = this.rootElement.querySelector('.onboarding-card');
1074
+ if (!card)
1075
+ return;
1076
+ // Add error mode class to card
1077
+ card.classList.add('onboarding-mode-error');
1078
+ card.classList.remove('onboarding-mode-loading');
1079
+ // Create or show error container
1080
+ let errorContainer = this.rootElement.querySelector('.onboarding-error-container');
1081
+ if (!errorContainer) {
1082
+ errorContainer = this.createElement('div', {
1083
+ className: 'onboarding-error-container',
1084
+ style: 'display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px 0; text-align: center;',
1085
+ });
1086
+ // Error Icon
1087
+ const iconContainer = this.createElement('div', {
1088
+ style: 'width: 64px; height: 64px; background-color: #fee2e2; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 20px;',
1089
+ });
1090
+ // Dark mode adjustment for icon container
1091
+ if (this.config.theme === 'dark') {
1092
+ iconContainer.style.backgroundColor = 'rgba(220, 38, 38, 0.2)';
1093
+ }
1094
+ const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
1095
+ iconSvg.setAttribute('width', '32');
1096
+ iconSvg.setAttribute('height', '32');
1097
+ iconSvg.setAttribute('viewBox', '0 0 24 24');
1098
+ iconSvg.setAttribute('fill', 'none');
1099
+ iconSvg.setAttribute('stroke', '#dc2626');
1100
+ iconSvg.setAttribute('stroke-width', '2');
1101
+ iconSvg.setAttribute('stroke-linecap', 'round');
1102
+ iconSvg.setAttribute('stroke-linejoin', 'round');
1103
+ const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
1104
+ circle.setAttribute('cx', '12');
1105
+ circle.setAttribute('cy', '12');
1106
+ circle.setAttribute('r', '10');
1107
+ iconSvg.appendChild(circle);
1108
+ const line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
1109
+ line1.setAttribute('x1', '12');
1110
+ line1.setAttribute('y1', '8');
1111
+ line1.setAttribute('x2', '12');
1112
+ line1.setAttribute('y2', '12');
1113
+ iconSvg.appendChild(line1);
1114
+ const line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
1115
+ line2.setAttribute('x1', '12');
1116
+ line2.setAttribute('y1', '16');
1117
+ line2.setAttribute('x2', '12.01');
1118
+ line2.setAttribute('y2', '16');
1119
+ iconSvg.appendChild(line2);
1120
+ iconContainer.appendChild(iconSvg);
1121
+ errorContainer.appendChild(iconContainer);
1122
+ // Title
1123
+ const title = this.createElement('h3', {
1124
+ textContent: 'Authentication Failed',
1125
+ style: 'font-size: 20px; font-weight: 600; margin: 0 0 12px 0; color: inherit;',
1126
+ });
1127
+ errorContainer.appendChild(title);
1128
+ // Message
1129
+ const msg = this.createElement('p', {
1130
+ textContent: message,
1131
+ style: 'font-size: 14px; color: #6b7280; margin: 0 0 24px 0; line-height: 1.5; max-width: 100%; word-break: break-word;',
1132
+ });
1133
+ if (this.config.theme === 'dark') {
1134
+ msg.style.color = '#9ca3af';
1135
+ }
1136
+ errorContainer.appendChild(msg);
1137
+ // OK Button
1138
+ const okButton = this.createElement('button', {
1139
+ className: 'onboarding-button onboarding-button-primary',
1140
+ textContent: 'OK',
1141
+ style: 'width: 100%; max-width: 200px;',
1142
+ });
1143
+ okButton.addEventListener('click', () => {
1144
+ if (this.config.modal) {
1145
+ this.close();
1146
+ }
1147
+ else {
1148
+ this.resetToLoginForm();
1149
+ }
1150
+ // Also clear URL params to prevent showing error again on refresh
1151
+ const url = new URL(window.location.href);
1152
+ url.searchParams.delete('error');
1153
+ window.history.replaceState({}, document.title, url.toString());
1154
+ });
1155
+ errorContainer.appendChild(okButton);
1156
+ card.appendChild(errorContainer);
1157
+ }
1158
+ else {
1159
+ // Update message if container exists
1160
+ const msgEl = errorContainer.querySelector('p');
1161
+ if (msgEl)
1162
+ msgEl.textContent = message;
1163
+ errorContainer.style.display = 'flex';
1164
+ }
1165
+ // Move footer to the bottom
1166
+ if (this.footer) {
1167
+ card.appendChild(this.footer);
1168
+ }
1169
+ }
1170
+ /**
1171
+ * Show loading screen
1172
+ */
1173
+ showLoadingScreen() {
1174
+ if (!this.rootElement)
1175
+ return;
1176
+ // If inline component, show loading as a modal overlay
1177
+ if (!this.config.modal) {
1178
+ this.showLoadingModal();
1179
+ return;
1180
+ }
1181
+ // Hide the main modal overlay to prevent showing wallet options below loading screen
1182
+ if (this.modalOverlay) {
1183
+ this.modalOverlay.style.display = 'none';
1184
+ }
1185
+ // Show loading modal overlay instead (with higher z-index)
1186
+ this.showLoadingModal();
1187
+ }
1188
+ /**
1189
+ * Show loading screen as a modal overlay (for inline components)
1190
+ */
1191
+ showLoadingModal() {
1192
+ // Hide any other modals that might be showing (wallet selection, etc.)
1193
+ const otherModals = document.querySelectorAll('.onboarding-modal-overlay, .external-wallet-modal, .wallet-modal-overlay');
1194
+ otherModals.forEach((modal) => {
1195
+ if (modal.id !== 'onboarding-loading-modal-overlay') {
1196
+ modal.style.display = 'none';
1197
+ }
1198
+ });
1199
+ // Create modal overlay if it doesn't exist
1200
+ let loadingModalOverlay = document.getElementById('onboarding-loading-modal-overlay');
1201
+ if (!loadingModalOverlay) {
1202
+ loadingModalOverlay = this.createElement('div', {
1203
+ id: 'onboarding-loading-modal-overlay',
1204
+ style: `
1205
+ position: fixed;
1206
+ top: 0;
1207
+ left: 0;
1208
+ right: 0;
1209
+ bottom: 0;
1210
+ background-color: rgba(0, 0, 0, 0.5);
1211
+ display: flex;
1212
+ align-items: center;
1213
+ justify-content: center;
1214
+ z-index: 9999999;
1215
+ backdrop-filter: blur(4px);
1216
+ `,
1217
+ });
1218
+ const loadingCard = this.createElement('div', {
1219
+ className: `onboarding-card onboarding-theme-${this.config.theme}`,
1220
+ style: `
1221
+ background: ${this.config.theme === 'dark' ? '#1f2937' : 'white'};
1222
+ border-radius: 16px;
1223
+ padding: 32px 32px 28px;
1224
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
1225
+ display: flex;
1226
+ flex-direction: column;
1227
+ align-items: center;
1228
+ justify-content: center;
1229
+ width: 420px;
1230
+ max-width: 420px;
1231
+ min-width: 420px;
1232
+ gap: 20px;
1233
+ box-sizing: border-box;
1234
+ `,
1235
+ });
1236
+ // Header with logo
1237
+ const header = this.createElement('div', {
1238
+ className: 'onboarding-header',
1239
+ style: 'display: flex; flex-direction: column; align-items: center; gap: 8px; width: 100%;',
1240
+ });
1241
+ const logoSection = this.createElement('div', { className: 'onboarding-logo-section' });
1242
+ if (this.config.logo) {
1243
+ const logoContainer = this.createElement('div', { className: 'onboarding-logo-container' });
1244
+ const logoImg = this.createElement('img', {
1245
+ src: this.config.logo,
1246
+ alt: 'Abstraxn',
1247
+ className: 'onboarding-logo-img',
1248
+ });
1249
+ logoContainer.appendChild(logoImg);
1250
+ logoSection.appendChild(logoContainer);
1251
+ }
1252
+ header.appendChild(logoSection);
1253
+ loadingCard.appendChild(header);
1254
+ // Spinner
1255
+ const spinnerColor = this.config.theme === 'dark' ? '#9ca3af' : '#111827';
1256
+ const spinnerBorderColor = this.config.theme === 'dark' ? 'rgba(156, 163, 175, 0.2)' : 'rgba(17, 24, 39, 0.1)';
1257
+ const spinner = this.createElement('div', {
1258
+ className: 'onboarding-loading-spinner',
1259
+ style: `
1260
+ width: 40px;
1261
+ height: 40px;
1262
+ border: 3px solid ${spinnerBorderColor};
1263
+ border-radius: 50%;
1264
+ border-top-color: ${spinnerColor};
1265
+ animation: onboarding-spin 1s ease-in-out infinite;
1266
+ margin-bottom: 16px;
1267
+ `,
1268
+ });
1269
+ // Add keyframes if not exists
1270
+ if (!document.getElementById('onboarding-spin-style')) {
1271
+ const style = document.createElement('style');
1272
+ style.id = 'onboarding-spin-style';
1273
+ style.textContent = `
1274
+ @keyframes onboarding-spin {
1275
+ to { transform: rotate(360deg); }
1276
+ }
1277
+ `;
1278
+ document.head.appendChild(style);
1279
+ }
1280
+ // Text
1281
+ const text = this.createElement('p', {
1282
+ textContent: 'Verifying login...',
1283
+ style: `
1284
+ font-size: 16px;
1285
+ color: ${this.config.theme === 'dark' ? '#e5e7eb' : '#374151'};
1286
+ margin: 0;
1287
+ font-weight: 500;
1288
+ `,
1289
+ });
1290
+ loadingCard.appendChild(spinner);
1291
+ loadingCard.appendChild(text);
1292
+ // Footer "Powered by Abstraxn"
1293
+ if (this.config.showFooter !== false) {
1294
+ const textColor = this.config.colors?.text || (this.config.theme === 'dark' ? '#9ca3af' : '#6b7280');
1295
+ const brandColor = this.config.colors?.text || (this.config.theme === 'dark' ? '#e5e7eb' : '#374151');
1296
+ const footer = this.createElement('div', { className: 'onboarding-footer' });
1297
+ const footerText = this.createElement('p', {
1298
+ className: 'onboarding-footer-text',
1299
+ style: `color: ${textColor}; display: flex; align-items: center; gap: 6px; justify-content: center; font-size: 11px; margin: 0;`,
1300
+ });
1301
+ const poweredBySpan = this.createElement('span', { textContent: 'Powered by', style: `color: ${textColor};` });
1302
+ footerText.appendChild(poweredBySpan);
1303
+ const brandContainer = this.createElement('a', {
1304
+ href: 'https://www.abstraxn.com/',
1305
+ target: '_blank',
1306
+ rel: 'noopener noreferrer',
1307
+ style: 'display: inline-flex; align-items: center; gap: 6px; text-decoration: none; cursor: pointer;',
1308
+ 'aria-label': 'Visit Abstraxn website',
1309
+ });
1310
+ const logoSvg = this.createElement('span', {
1311
+ innerHTML: `
1312
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none" style="width: 20px; height: 20px; display: inline-block; vertical-align: middle;">
1313
+ <path d="M47.9082 37.191L23.9541 23.553L0 9.91504L37.7436 -8.13502e-06L47.9082 37.191Z" fill="url(#paint0_linear_1242_781)"/>
1314
+ <path d="M13.5156 46.3594L10.1846 47.2344L8.99219 42.873L12.4365 42.4111L13.5156 46.3594ZM20.4268 44.5566L17.1152 45.4199L16.125 41.8818L19.5566 41.3721L20.4268 44.5566ZM26.9307 42.834L23.6152 43.6982L22.8438 40.9443L26.2744 40.4326L26.9307 42.834ZM33.5078 41.1074L30.1875 41.9727L29.6328 39.9951L33.0635 39.4844L33.5078 41.1074ZM39.998 39.4014L36.6729 40.2686L36.3359 39.0674L39.7656 38.5508L39.998 39.4014ZM10.1406 33.9434L11.6807 39.5762L8.15918 39.7568L6.46094 33.543L10.1406 33.9434ZM17.6582 34.5996L18.8916 39.1113L15.4199 39.3496L14.0078 34.3076L17.6582 34.5996ZM24.8779 35.332L25.8047 38.7227L22.3252 38.959L21.2266 35.0361L24.8779 35.332ZM32.0918 36.0723L32.7119 38.3398L29.2227 38.5723L28.4375 35.7656L32.0918 36.0723ZM43.2373 38.5488L43.1172 38.1172L47.7939 37.3516L43.2373 38.5488ZM39.3047 36.8096L39.6182 37.9561L36.1221 38.1846L35.6484 36.4961L39.3047 36.8096ZM47.7939 37.3516L43.0107 37.7939L42.8516 37.2236L47.7939 37.3516ZM47.8291 37.2988L42.7227 36.7529L42.5547 36.1523L47.8291 37.2988ZM47.8652 37.2412L42.3711 35.3486L42.2031 34.7471L47.8652 37.2412ZM38.748 34.8604L39.0664 36.0254L35.3369 35.4043L34.8594 33.6982L38.748 34.8604ZM31.0576 32.3994L31.6787 34.6709L27.959 34.0566L27.1719 31.2461L31.0576 32.3994ZM38.0811 32.6318L38.4092 33.8301L34.4893 32.3496L34.1543 31.1523C34.1005 30.9591 34.0322 30.7719 33.9531 30.5918L38.0811 32.6318ZM23.3682 29.9385L24.291 33.3164L20.5801 32.71L19.4844 28.7949L23.3682 29.9385ZM15.6641 27.4805L16.8896 31.9629L13.1875 31.3594L11.7812 26.3379L15.6641 27.4805ZM29.9463 28.4678L30.5693 30.748L26.6201 29.2812L25.8633 26.583C25.8534 26.5431 25.8417 26.5028 25.8281 26.4639L29.9463 28.4678ZM7.6875 25.0938L9.21484 30.6816L5.44727 29.959L3.75781 23.7793L7.6875 25.0938ZM21.5312 23.3623L22.4492 26.7227L18.5059 25.2627L17.4141 21.3643L21.5312 23.3623ZM13.6104 20.1602L14.8242 24.6006L10.8887 23.1387L9.49219 18.1553L13.6104 20.1602ZM5.2168 16.0654L6.72559 21.585L2.70703 19.9473L1.06055 13.8701L5.2168 16.0654Z" fill="url(#paint1_linear_1242_781)"/>
1315
+ <defs>
1316
+ <linearGradient id="paint0_linear_1242_781" x1="7.03362" y1="6.09543" x2="47.2003" y2="57.0656" gradientUnits="userSpaceOnUse">
1317
+ <stop stop-color="#F878D2"/>
1318
+ <stop offset="0.393522" stop-color="#FDCC81"/>
1319
+ <stop offset="1" stop-color="#F9A0D9"/>
1320
+ </linearGradient>
1321
+ <linearGradient id="paint1_linear_1242_781" x1="2.53739" y1="21.5136" x2="48.7969" y2="38.5742" gradientUnits="userSpaceOnUse">
1322
+ <stop stop-color="#F878D2"/>
1323
+ <stop offset="0.393522" stop-color="#FDCC81"/>
1324
+ <stop offset="1" stop-color="#F9A0D9"/>
1325
+ </linearGradient>
1326
+ </defs>
1327
+ </svg>
1328
+ `,
1329
+ style: 'display: inline-flex; align-items: center;',
1330
+ });
1331
+ brandContainer.appendChild(logoSvg);
1332
+ const brandText = this.createElement('span', {
1333
+ textContent: 'abstraxn',
1334
+ style: `color: ${brandColor}; font-weight: 700; letter-spacing: 0.2px;`,
1335
+ });
1336
+ brandContainer.appendChild(brandText);
1337
+ footerText.appendChild(brandContainer);
1338
+ footer.appendChild(footerText);
1339
+ loadingCard.appendChild(footer);
1340
+ }
1341
+ loadingModalOverlay.appendChild(loadingCard);
1342
+ document.body.appendChild(loadingModalOverlay);
1343
+ }
1344
+ else {
1345
+ loadingModalOverlay.style.display = 'flex';
1346
+ }
1347
+ // Prevent body scroll
1348
+ document.body.style.overflow = 'hidden';
1349
+ }
1350
+ /**
1351
+ * Hide loading modal overlay
1352
+ */
1353
+ hideLoadingModal() {
1354
+ const loadingModalOverlay = document.getElementById('onboarding-loading-modal-overlay');
1355
+ if (loadingModalOverlay) {
1356
+ loadingModalOverlay.style.display = 'none';
1357
+ document.body.style.overflow = '';
1358
+ }
1359
+ }
1360
+ /**
1361
+ * Show error screen as a modal overlay (for inline components)
1362
+ */
1363
+ showErrorModal(message) {
1364
+ // Helper function to close error modal
1365
+ const closeErrorModal = () => {
1366
+ const overlay = document.getElementById('onboarding-error-modal-overlay');
1367
+ if (overlay) {
1368
+ overlay.style.display = 'none';
1369
+ document.body.style.overflow = '';
1370
+ // Clear URL params
1371
+ const url = new URL(window.location.href);
1372
+ url.searchParams.delete('error');
1373
+ window.history.replaceState({}, document.title, url.toString());
1374
+ }
1375
+ };
1376
+ // Create modal overlay if it doesn't exist
1377
+ let errorModalOverlay = document.getElementById('onboarding-error-modal-overlay');
1378
+ if (!errorModalOverlay) {
1379
+ errorModalOverlay = this.createElement('div', {
1380
+ id: 'onboarding-error-modal-overlay',
1381
+ style: `
1382
+ position: fixed;
1383
+ top: 0;
1384
+ left: 0;
1385
+ right: 0;
1386
+ bottom: 0;
1387
+ background-color: rgba(0, 0, 0, 0.5);
1388
+ display: flex;
1389
+ align-items: center;
1390
+ justify-content: center;
1391
+ z-index: 999999;
1392
+ backdrop-filter: blur(4px);
1393
+ `,
1394
+ });
1395
+ // Add backdrop click handler to close modal
1396
+ errorModalOverlay.addEventListener('click', (e) => {
1397
+ if (e.target === errorModalOverlay) {
1398
+ closeErrorModal();
1399
+ }
1400
+ });
1401
+ const errorCard = this.createElement('div', {
1402
+ className: `onboarding-card onboarding-theme-${this.config.theme}`,
1403
+ style: `
1404
+ background: ${this.config.theme === 'dark' ? '#1f2937' : 'white'};
1405
+ border-radius: 16px;
1406
+ padding: 40px;
1407
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
1408
+ display: flex;
1409
+ flex-direction: column;
1410
+ align-items: center;
1411
+ justify-content: center;
1412
+ width: 420px;
1413
+ max-width: 420px;
1414
+ min-width: 420px;
1415
+ text-align: center;
1416
+ position: relative;
1417
+ box-sizing: border-box;
1418
+ `,
1419
+ });
1420
+ // Prevent clicks on card from closing modal
1421
+ errorCard.addEventListener('click', (e) => {
1422
+ e.stopPropagation();
1423
+ });
1424
+ // Header with logo
1425
+ const header = this.createElement('div', {
1426
+ className: 'onboarding-header',
1427
+ style: 'display: flex; flex-direction: column; align-items: center; gap: 8px; width: 100%; margin-bottom: 24px;',
1428
+ });
1429
+ const logoSection = this.createElement('div', { className: 'onboarding-logo-section' });
1430
+ if (this.config.logo) {
1431
+ const logoContainer = this.createElement('div', { className: 'onboarding-logo-container' });
1432
+ const logoImg = this.createElement('img', {
1433
+ src: this.config.logo,
1434
+ alt: 'Abstraxn',
1435
+ className: 'onboarding-logo-img',
1436
+ });
1437
+ logoContainer.appendChild(logoImg);
1438
+ logoSection.appendChild(logoContainer);
1439
+ }
1440
+ header.appendChild(logoSection);
1441
+ errorCard.appendChild(header);
1442
+ // Error Icon
1443
+ const iconContainer = this.createElement('div', {
1444
+ style: 'width: 64px; height: 64px; background-color: #fee2e2; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 20px;',
1445
+ });
1446
+ if (this.config.theme === 'dark') {
1447
+ iconContainer.style.backgroundColor = 'rgba(220, 38, 38, 0.2)';
1448
+ }
1449
+ const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
1450
+ iconSvg.setAttribute('width', '32');
1451
+ iconSvg.setAttribute('height', '32');
1452
+ iconSvg.setAttribute('viewBox', '0 0 24 24');
1453
+ iconSvg.setAttribute('fill', 'none');
1454
+ iconSvg.setAttribute('stroke', '#dc2626');
1455
+ iconSvg.setAttribute('stroke-width', '2');
1456
+ iconSvg.setAttribute('stroke-linecap', 'round');
1457
+ iconSvg.setAttribute('stroke-linejoin', 'round');
1458
+ const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
1459
+ circle.setAttribute('cx', '12');
1460
+ circle.setAttribute('cy', '12');
1461
+ circle.setAttribute('r', '10');
1462
+ iconSvg.appendChild(circle);
1463
+ const line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
1464
+ line1.setAttribute('x1', '12');
1465
+ line1.setAttribute('y1', '8');
1466
+ line1.setAttribute('x2', '12');
1467
+ line1.setAttribute('y2', '12');
1468
+ iconSvg.appendChild(line1);
1469
+ const line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
1470
+ line2.setAttribute('x1', '12');
1471
+ line2.setAttribute('y1', '16');
1472
+ line2.setAttribute('x2', '12.01');
1473
+ line2.setAttribute('y2', '16');
1474
+ iconSvg.appendChild(line2);
1475
+ iconContainer.appendChild(iconSvg);
1476
+ errorCard.appendChild(iconContainer);
1477
+ // Title
1478
+ const title = this.createElement('h3', {
1479
+ textContent: 'Authentication Failed',
1480
+ style: `font-size: 20px; font-weight: 600; margin: 0 0 12px 0; color: ${this.config.theme === 'dark' ? '#ffffff' : '#111827'};`,
1481
+ });
1482
+ errorCard.appendChild(title);
1483
+ // Message
1484
+ const msg = this.createElement('p', {
1485
+ textContent: message,
1486
+ style: `font-size: 14px; color: ${this.config.theme === 'dark' ? '#e5e7eb' : '#374151'}; margin: 0 0 24px 0; line-height: 1.5; max-width: 100%; word-break: break-word;`,
1487
+ });
1488
+ errorCard.appendChild(msg);
1489
+ // OK Button
1490
+ const okButton = this.createElement('button', {
1491
+ className: 'onboarding-button onboarding-button-primary',
1492
+ textContent: 'OK',
1493
+ style: 'width: 100%; max-width: 200px;',
1494
+ });
1495
+ okButton.addEventListener('click', closeErrorModal);
1496
+ errorCard.appendChild(okButton);
1497
+ // Footer "Powered by Abstraxn"
1498
+ if (this.config.showFooter !== false) {
1499
+ const textColor = this.config.colors?.text || (this.config.theme === 'dark' ? '#9ca3af' : '#6b7280');
1500
+ const brandColor = this.config.colors?.text || (this.config.theme === 'dark' ? '#e5e7eb' : '#374151');
1501
+ const footer = this.createElement('div', { className: 'onboarding-footer' });
1502
+ const footerText = this.createElement('p', {
1503
+ className: 'onboarding-footer-text',
1504
+ style: `color: ${textColor}; display: flex; align-items: center; gap: 6px; justify-content: center; font-size: 11px; margin: 0; margin-top: 24px;`,
1505
+ });
1506
+ const poweredBySpan = this.createElement('span', { textContent: 'Powered by', style: `color: ${textColor};` });
1507
+ footerText.appendChild(poweredBySpan);
1508
+ const brandContainer = this.createElement('a', {
1509
+ href: 'https://www.abstraxn.com/',
1510
+ target: '_blank',
1511
+ rel: 'noopener noreferrer',
1512
+ style: 'display: inline-flex; align-items: center; gap: 6px; text-decoration: none; cursor: pointer;',
1513
+ 'aria-label': 'Visit Abstraxn website',
1514
+ });
1515
+ const logoSvg = this.createElement('span', {
1516
+ innerHTML: `
1517
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none" style="width: 20px; height: 20px; display: inline-block; vertical-align: middle;">
1518
+ <path d="M47.9082 37.191L23.9541 23.553L0 9.91504L37.7436 -8.13502e-06L47.9082 37.191Z" fill="url(#paint0_linear_error_modal)"/>
1519
+ <path d="M13.5156 46.3594L10.1846 47.2344L8.99219 42.873L12.4365 42.4111L13.5156 46.3594ZM20.4268 44.5566L17.1152 45.4199L16.125 41.8818L19.5566 41.3721L20.4268 44.5566ZM26.9307 42.834L23.6152 43.6982L22.8438 40.9443L26.2744 40.4326L26.9307 42.834ZM33.5078 41.1074L30.1875 41.9727L29.6328 39.9951L33.0635 39.4844L33.5078 41.1074ZM39.998 39.4014L36.6729 40.2686L36.3359 39.0674L39.7656 38.5508L39.998 39.4014ZM10.1406 33.9434L11.6807 39.5762L8.15918 39.7568L6.46094 33.543L10.1406 33.9434ZM17.6582 34.5996L18.8916 39.1113L15.4199 39.3496L14.0078 34.3076L17.6582 34.5996ZM24.8779 35.332L25.8047 38.7227L22.3252 38.959L21.2266 35.0361L24.8779 35.332ZM32.0918 36.0723L32.7119 38.3398L29.2227 38.5723L28.4375 35.7656L32.0918 36.0723ZM43.2373 38.5488L43.1172 38.1172L47.7939 37.3516L43.2373 38.5488ZM39.3047 36.8096L39.6182 37.9561L36.1221 38.1846L35.6484 36.4961L39.3047 36.8096ZM47.7939 37.3516L43.0107 37.7939L42.8516 37.2236L47.7939 37.3516ZM47.8291 37.2988L42.7227 36.7529L42.5547 36.1523L47.8291 37.2988ZM47.8652 37.2412L42.3711 35.3486L42.2031 34.7471L47.8652 37.2412ZM38.748 34.8604L39.0664 36.0254L35.3369 35.4043L34.8594 33.6982L38.748 34.8604ZM31.0576 32.3994L31.6787 34.6709L27.959 34.0566L27.1719 31.2461L31.0576 32.3994ZM38.0811 32.6318L38.4092 33.8301L34.4893 32.3496L34.1543 31.1523C34.1005 30.9591 34.0322 30.7719 33.9531 30.5918L38.0811 32.6318ZM23.3682 29.9385L24.291 33.3164L20.5801 32.71L19.4844 28.7949L23.3682 29.9385ZM15.6641 27.4805L16.8896 31.9629L13.1875 31.3594L11.7812 26.3379L15.6641 27.4805ZM29.9463 28.4678L30.5693 30.748L26.6201 29.2812L25.8633 26.583C25.8534 26.5431 25.8417 26.5028 25.8281 26.4639L29.9463 28.4678ZM7.6875 25.0938L9.21484 30.6816L5.44727 29.959L3.75781 23.7793L7.6875 25.0938ZM21.5312 23.3623L22.4492 26.7227L18.5059 25.2627L17.4141 21.3643L21.5312 23.3623ZM13.6104 20.1602L14.8242 24.6006L10.8887 23.1387L9.49219 18.1553L13.6104 20.1602ZM5.2168 16.0654L6.72559 21.585L2.70703 19.9473L1.06055 13.8701L5.2168 16.0654Z" fill="url(#paint1_linear_error_modal)"/>
1520
+ <defs>
1521
+ <linearGradient id="paint0_linear_error_modal" x1="7.03362" y1="6.09543" x2="47.2003" y2="57.0656" gradientUnits="userSpaceOnUse">
1522
+ <stop stop-color="#F878D2"/>
1523
+ <stop offset="0.393522" stop-color="#FDCC81"/>
1524
+ <stop offset="1" stop-color="#F9A0D9"/>
1525
+ </linearGradient>
1526
+ <linearGradient id="paint1_linear_error_modal" x1="2.53739" y1="21.5136" x2="48.7969" y2="38.5742" gradientUnits="userSpaceOnUse">
1527
+ <stop stop-color="#F878D2"/>
1528
+ <stop offset="0.393522" stop-color="#FDCC81"/>
1529
+ <stop offset="1" stop-color="#F9A0D9"/>
1530
+ </linearGradient>
1531
+ </defs>
1532
+ </svg>
1533
+ `,
1534
+ style: 'display: inline-flex; align-items: center;',
1535
+ });
1536
+ brandContainer.appendChild(logoSvg);
1537
+ const brandText = this.createElement('span', {
1538
+ textContent: 'abstraxn',
1539
+ style: `color: ${brandColor}; font-weight: 700; letter-spacing: 0.2px;`,
1540
+ });
1541
+ brandContainer.appendChild(brandText);
1542
+ footerText.appendChild(brandContainer);
1543
+ footer.appendChild(footerText);
1544
+ errorCard.appendChild(footer);
1545
+ }
1546
+ errorModalOverlay.appendChild(errorCard);
1547
+ document.body.appendChild(errorModalOverlay);
1548
+ }
1549
+ else {
1550
+ // Update message if overlay exists
1551
+ const msgEl = errorModalOverlay.querySelector('p');
1552
+ if (msgEl)
1553
+ msgEl.textContent = message;
1554
+ errorModalOverlay.style.display = 'flex';
1555
+ }
1556
+ // Prevent body scroll
1557
+ document.body.style.overflow = 'hidden';
1558
+ }
1559
+ /**
1560
+ * Inject CSS styles into the document head (only once)
1561
+ */
1562
+ injectStyles() {
1563
+ if (OnboardingUIWeb.stylesInjected) {
1564
+ return;
1565
+ }
1566
+ // Check if styles are already injected
1567
+ const existingStyle = document.getElementById('onboarding-ui-styles');
1568
+ if (existingStyle) {
1569
+ OnboardingUIWeb.stylesInjected = true;
1570
+ return;
1571
+ }
1572
+ // Create and inject style element
1573
+ const styleElement = document.createElement('style');
1574
+ styleElement.id = 'onboarding-ui-styles';
1575
+ styleElement.textContent = ONBOARDING_UI_STYLES;
1576
+ document.head.appendChild(styleElement);
1577
+ // Inject custom CSS if provided
1578
+ if (this.config.customCSS) {
1579
+ const customStyleElement = document.createElement('style');
1580
+ customStyleElement.id = 'onboarding-ui-custom-styles';
1581
+ customStyleElement.textContent = this.config.customCSS;
1582
+ document.head.appendChild(customStyleElement);
1583
+ }
1584
+ // Apply custom colors if provided
1585
+ if (this.config.colors) {
1586
+ this.applyCustomColors();
1587
+ }
1588
+ OnboardingUIWeb.stylesInjected = true;
1589
+ // Inject utility styles for loading/error modes
1590
+ const utilityStyle = document.createElement('style');
1591
+ utilityStyle.id = 'onboarding-utility-styles';
1592
+ utilityStyle.textContent = `
1593
+ .onboarding-card.onboarding-mode-loading > *:not(.onboarding-loading-container):not(.onboarding-header):not(.onboarding-footer),
1594
+ .onboarding-card.onboarding-mode-error > *:not(.onboarding-error-container):not(.onboarding-header):not(.onboarding-footer) {
1595
+ display: none !important;
1596
+ }
1597
+ `;
1598
+ document.head.appendChild(utilityStyle);
1599
+ }
1600
+ /**
1601
+ * Apply custom colors from config
1602
+ */
1603
+ applyCustomColors() {
1604
+ if (!this.config.colors)
1605
+ return;
1606
+ const root = this.rootElement || document.documentElement;
1607
+ const style = document.createElement('style');
1608
+ style.id = 'onboarding-ui-colors';
1609
+ let css = '';
1610
+ if (this.config.colors.primary) {
1611
+ css += `.onboarding-button-primary { background-color: ${this.config.colors.primary} !important; background: ${this.config.colors.primary} !important; }`;
1612
+ css += `.onboarding-footer-brand { color: ${this.config.colors.primary} !important; }`;
1613
+ css += `.onboarding-passkey-signup-link { color: ${this.config.colors.primary} !important; }`;
1614
+ }
1615
+ if (this.config.colors.primaryHover) {
1616
+ css += `.onboarding-button-primary:hover:not(:disabled) { background-color: ${this.config.colors.primaryHover} !important; background: ${this.config.colors.primaryHover} !important; }`;
1617
+ }
1618
+ if (this.config.colors.background) {
1619
+ css += `.onboarding-card { background-color: ${this.config.colors.background} !important; }`;
1620
+ }
1621
+ if (this.config.colors.text) {
1622
+ css += `.onboarding-card { color: ${this.config.colors.text} !important; }`;
1623
+ }
1624
+ if (this.config.colors.border) {
1625
+ css += `.onboarding-input { border-color: ${this.config.colors.border} !important; }`;
1626
+ }
1627
+ if (this.config.colors.error) {
1628
+ css += `.onboarding-error { background-color: ${this.config.colors.error} !important; }`;
1629
+ }
1630
+ style.textContent = css;
1631
+ document.head.appendChild(style);
1632
+ }
1633
+ /**
1634
+ * Setup the container element
1635
+ */
1636
+ setupContainer() {
1637
+ // For modal mode, always use body
1638
+ if (this.config.modal) {
1639
+ this.container = document.body;
1640
+ }
1641
+ else {
1642
+ if (this.config.container) {
1643
+ if (typeof this.config.container === 'string') {
1644
+ this.container = document.querySelector(this.config.container);
1645
+ if (!this.container) {
1646
+ throw new Error(`Container element not found: ${this.config.container}`);
1647
+ }
1648
+ }
1649
+ else {
1650
+ this.container = this.config.container;
1651
+ }
1652
+ }
1653
+ else {
1654
+ // Create a default container
1655
+ this.container = document.body;
1656
+ }
1657
+ }
1658
+ }
1659
+ /**
1660
+ * Render the component
1661
+ */
1662
+ render() {
1663
+ if (!this.container)
1664
+ return;
1665
+ if (this.config.modal) {
1666
+ this.renderAsModal();
1667
+ }
1668
+ else {
1669
+ this.renderInline();
1670
+ }
1671
+ }
1672
+ /**
1673
+ * Render as modal overlay
1674
+ */
1675
+ renderAsModal() {
1676
+ // Create modal overlay (backdrop) - start hidden to prevent flicker
1677
+ this.modalOverlay = this.createElement('div', {
1678
+ className: 'onboarding-modal-overlay',
1679
+ style: 'display: none;', // Start hidden by default
1680
+ });
1681
+ // Create modal content wrapper
1682
+ this.modalContent = this.createElement('div', {
1683
+ className: `onboarding-modal-content onboarding-theme-${this.config.theme}`,
1684
+ role: 'dialog',
1685
+ 'aria-modal': 'true',
1686
+ 'aria-labelledby': 'onboarding-title',
1687
+ tabIndex: -1,
1688
+ });
1689
+ // Create root element for the form
1690
+ this.rootElement = this.createElement('div', {
1691
+ className: `onboarding-container onboarding-theme-${this.config.theme} ${this.config.className}`,
1692
+ style: this.parseStyle(this.config.style),
1693
+ });
1694
+ // Close button
1695
+ if (this.config.showCloseButton) {
1696
+ this.closeButton = this.createElement('button', {
1697
+ className: 'onboarding-modal-close',
1698
+ type: 'button',
1699
+ innerHTML: '×',
1700
+ 'aria-label': 'Close modal',
1701
+ 'aria-describedby': 'close-button-description',
1702
+ });
1703
+ this.closeButton.addEventListener('click', () => this.close());
1704
+ this.closeButton.addEventListener('keydown', (e) => {
1705
+ if (e.key === 'Enter' || e.key === ' ') {
1706
+ e.preventDefault();
1707
+ this.close();
1708
+ }
1709
+ });
1710
+ this.modalContent.appendChild(this.closeButton);
1711
+ }
1712
+ // Build the form content
1713
+ this.buildFormContent();
1714
+ // Append form to root, root to modal content, modal content to overlay
1715
+ this.modalContent.appendChild(this.rootElement);
1716
+ this.modalOverlay.appendChild(this.modalContent);
1717
+ // Add click handler for backdrop
1718
+ if (this.config.closeOnBackdropClick) {
1719
+ this.modalOverlay.addEventListener('click', (e) => {
1720
+ if (e.target === this.modalOverlay) {
1721
+ this.close();
1722
+ }
1723
+ });
1724
+ }
1725
+ // Prevent body scroll when modal is open
1726
+ document.body.style.overflow = 'hidden';
1727
+ // Append to body
1728
+ document.body.appendChild(this.modalOverlay);
1729
+ // Add animation class
1730
+ setTimeout(() => {
1731
+ if (this.modalOverlay) {
1732
+ this.modalOverlay.classList.add('onboarding-modal-open');
1733
+ // Focus management for accessibility
1734
+ if (this.emailInput) {
1735
+ this.emailInput.focus();
1736
+ }
1737
+ }
1738
+ }, 10);
1739
+ // Add keyboard navigation (ESC to close)
1740
+ this.modalOverlay.addEventListener('keydown', (e) => {
1741
+ if (e.key === 'Escape' && this.config.showCloseButton) {
1742
+ this.close();
1743
+ }
1744
+ // Trap focus within modal
1745
+ if (e.key === 'Tab') {
1746
+ this.trapFocus(e);
1747
+ }
1748
+ });
1749
+ }
1750
+ /**
1751
+ * Trap focus within modal for accessibility
1752
+ */
1753
+ trapFocus(e) {
1754
+ if (!this.modalContent)
1755
+ return;
1756
+ const focusableElements = this.modalContent.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
1757
+ const firstElement = focusableElements[0];
1758
+ const lastElement = focusableElements[focusableElements.length - 1];
1759
+ if (e.shiftKey && document.activeElement === firstElement) {
1760
+ e.preventDefault();
1761
+ lastElement.focus();
1762
+ }
1763
+ else if (!e.shiftKey && document.activeElement === lastElement) {
1764
+ e.preventDefault();
1765
+ firstElement.focus();
1766
+ }
1767
+ }
1768
+ /**
1769
+ * Render inline (non-modal)
1770
+ */
1771
+ renderInline() {
1772
+ if (!this.container)
1773
+ return;
1774
+ // Create root element
1775
+ this.rootElement = this.createElement('div', {
1776
+ className: `onboarding-container onboarding-inline onboarding-theme-${this.config.theme} ${this.config.className}`,
1777
+ style: this.parseStyle(this.config.style),
1778
+ });
1779
+ // Build the form content
1780
+ this.buildFormContent();
1781
+ // Append to container
1782
+ if (this.container === document.body) {
1783
+ this.container.appendChild(this.rootElement);
1784
+ }
1785
+ else {
1786
+ this.container.innerHTML = '';
1787
+ this.container.appendChild(this.rootElement);
1788
+ }
1789
+ }
1790
+ /**
1791
+ * Build form content (shared between modal and inline)
1792
+ */
1793
+ buildFormContent() {
1794
+ if (!this.rootElement)
1795
+ return;
1796
+ // Create card
1797
+ const card = this.createElement('div', { className: 'onboarding-card' });
1798
+ // Header with logo at top
1799
+ const header = this.createElement('div', { className: 'onboarding-header' });
1800
+ // Logo section (centered at top)
1801
+ const logoSection = this.createElement('div', { className: 'onboarding-logo-section' });
1802
+ if (this.config.logo) {
1803
+ const logoContainer = this.createElement('div', { className: 'onboarding-logo-container' });
1804
+ const logoImg = this.createElement('img', {
1805
+ src: this.config.logo,
1806
+ alt: 'Logo',
1807
+ className: 'onboarding-logo-img',
1808
+ });
1809
+ logoContainer.appendChild(logoImg);
1810
+ logoSection.appendChild(logoContainer);
1811
+ }
1812
+ header.appendChild(logoSection);
1813
+ // Onboarding title (always shown, default: "Sign in")
1814
+ const title = this.createElement('h1', {
1815
+ className: 'onboarding-title',
1816
+ textContent: this.config.onboardTitle || 'Sign in',
1817
+ id: 'onboarding-title',
1818
+ });
1819
+ header.appendChild(title);
1820
+ // Form
1821
+ this.emailForm = this.createElement('form', {
1822
+ className: 'onboarding-form',
1823
+ 'aria-label': 'Sign in form',
1824
+ noValidate: true,
1825
+ });
1826
+ // Email input group
1827
+ const emailGroup = this.createElement('div', { className: 'onboarding-input-group' });
1828
+ // Email label
1829
+ const emailLabel = this.createElement('label', {
1830
+ className: 'onboarding-input-label',
1831
+ textContent: this.config.labels?.emailLabel || 'Email Address',
1832
+ htmlFor: 'onboarding-email-input',
1833
+ });
1834
+ emailGroup.appendChild(emailLabel);
1835
+ const emailWrapper = this.createElement('div', { className: 'onboarding-input-wrapper' });
1836
+ // Email icon SVG
1837
+ const emailIcon = this.createEmailIcon();
1838
+ emailWrapper.appendChild(emailIcon);
1839
+ // Get auth methods to check if all are enabled
1840
+ const authMethods = this.config.authMethods || ['otp', 'google'];
1841
+ const showEmail = authMethods.includes('otp');
1842
+ const showGoogle = authMethods.includes('google');
1843
+ const showTwitter = authMethods.includes('twitter');
1844
+ const showDiscord = authMethods.includes('discord');
1845
+ const showPasskey = authMethods.includes('passkey');
1846
+ const socialMethods = [
1847
+ { key: 'google', show: showGoogle },
1848
+ { key: 'twitter', show: showTwitter },
1849
+ { key: 'discord', show: showDiscord },
1850
+ ].filter((m) => m.show);
1851
+ // Treat “all configs” as email + passkey + all socials present
1852
+ const allAuthMethodsEnabled = showEmail && showPasskey && showGoogle && showTwitter && showDiscord;
1853
+ // Email input
1854
+ this.emailInput = this.createElement('input', {
1855
+ type: 'email',
1856
+ id: 'onboarding-email-input',
1857
+ placeholder: this.config.labels?.emailPlaceholder || 'Enter your email',
1858
+ className: 'onboarding-input',
1859
+ required: true,
1860
+ autocomplete: 'email',
1861
+ 'aria-label': this.config.labels?.emailLabel || 'Email Address',
1862
+ 'aria-required': 'true',
1863
+ style: allAuthMethodsEnabled ? 'padding-right: 48px;' : '',
1864
+ });
1865
+ emailWrapper.appendChild(this.emailInput);
1866
+ // Add right arrow button inside input when all auth methods are enabled
1867
+ if (allAuthMethodsEnabled) {
1868
+ this.emailArrowButton = this.createElement('button', {
1869
+ type: 'button',
1870
+ className: 'onboarding-input-arrow-button',
1871
+ 'aria-label': this.config.labels?.emailButton || 'Continue with email',
1872
+ });
1873
+ const arrowIcon = this.createArrowIcon();
1874
+ arrowIcon.setAttribute('aria-hidden', 'true');
1875
+ this.emailArrowButton.appendChild(arrowIcon);
1876
+ // Add click handler to submit form
1877
+ this.emailArrowButton.addEventListener('click', (e) => {
1878
+ e.preventDefault();
1879
+ if (this.emailInput && this.emailInput.value.trim()) {
1880
+ this.emailForm?.requestSubmit();
1881
+ }
1882
+ });
1883
+ // Also submit on Enter key
1884
+ this.emailInput.addEventListener('keydown', (e) => {
1885
+ if (e.key === 'Enter' && this.emailInput?.value.trim()) {
1886
+ e.preventDefault();
1887
+ this.emailForm?.requestSubmit();
1888
+ }
1889
+ });
1890
+ emailWrapper.appendChild(this.emailArrowButton);
1891
+ }
1892
+ emailGroup.appendChild(emailWrapper);
1893
+ this.emailForm.appendChild(emailGroup);
1894
+ // OTP input group (initially hidden)
1895
+ this.otpGroup = this.createElement('div', {
1896
+ className: 'onboarding-input-group',
1897
+ style: 'display: none;',
1898
+ });
1899
+ // OTP label
1900
+ const otpLabel = this.createElement('label', {
1901
+ className: 'onboarding-input-label',
1902
+ textContent: this.config.labels?.otpLabel || 'Verification code',
1903
+ htmlFor: 'onboarding-otp-input',
1904
+ });
1905
+ this.otpGroup.appendChild(otpLabel);
1906
+ const otpWrapper = this.createElement('div', { className: 'onboarding-input-wrapper' });
1907
+ this.otpInput = this.createElement('input', {
1908
+ type: 'text',
1909
+ id: 'onboarding-otp-input',
1910
+ placeholder: this.config.labels?.otpPlaceholder || 'Enter 6-digit code',
1911
+ className: 'onboarding-input',
1912
+ required: false, // Don't require initially since it's hidden
1913
+ maxLength: 6,
1914
+ inputMode: 'numeric',
1915
+ pattern: '[0-9]*',
1916
+ autocomplete: 'one-time-code',
1917
+ 'aria-label': this.config.labels?.otpLabel || 'Verification code',
1918
+ });
1919
+ otpWrapper.appendChild(this.otpInput);
1920
+ this.otpGroup.appendChild(otpWrapper);
1921
+ this.emailForm.appendChild(this.otpGroup);
1922
+ // Error element
1923
+ this.errorElement = this.createElement('div', {
1924
+ className: 'onboarding-error',
1925
+ style: 'display: none;',
1926
+ role: 'alert',
1927
+ 'aria-live': 'polite',
1928
+ });
1929
+ this.emailForm.appendChild(this.errorElement);
1930
+ // Continue button (hide if all auth methods are enabled)
1931
+ this.continueButton = this.createElement('button', {
1932
+ type: 'submit',
1933
+ className: 'onboarding-button onboarding-button-primary',
1934
+ textContent: this.config.labels?.emailButton || 'Continue',
1935
+ 'aria-label': this.config.labels?.emailButton || 'Continue with email',
1936
+ style: allAuthMethodsEnabled ? 'display: none;' : '',
1937
+ });
1938
+ this.emailForm.appendChild(this.continueButton);
1939
+ card.appendChild(header);
1940
+ // Re-check auth methods for other elements (already checked above)
1941
+ // Only append email form if email auth is enabled
1942
+ if (showEmail) {
1943
+ card.appendChild(this.emailForm);
1944
+ }
1945
+ else {
1946
+ // Still create the form but hide it (needed for OTP flow)
1947
+ this.emailForm.style.display = 'none';
1948
+ card.appendChild(this.emailForm);
1949
+ }
1950
+ // Divider (show if email plus any social method is enabled)
1951
+ if (showEmail && socialMethods.length > 0) {
1952
+ this.divider = this.createElement('div', { className: 'onboarding-divider' });
1953
+ const dividerText = this.createElement('span', {
1954
+ className: 'onboarding-divider-text',
1955
+ textContent: 'or',
1956
+ });
1957
+ this.divider.appendChild(dividerText);
1958
+ card.appendChild(this.divider);
1959
+ }
1960
+ else {
1961
+ // Create divider element but hide it
1962
+ this.divider = this.createElement('div', {
1963
+ className: 'onboarding-divider',
1964
+ style: 'display: none;',
1965
+ });
1966
+ card.appendChild(this.divider);
1967
+ }
1968
+ // Social login buttons (Google, Twitter, Discord)
1969
+ if (socialMethods.length > 0) {
1970
+ if (socialMethods.length === 1) {
1971
+ const method = socialMethods[0].key;
1972
+ const isGoogle = method === 'google';
1973
+ const isTwitter = method === 'twitter';
1974
+ const isDiscord = method === 'discord';
1975
+ const button = this.createElement('button', {
1976
+ type: 'button',
1977
+ className: isGoogle ? 'onboarding-button onboarding-button-google' : 'onboarding-button onboarding-button-social',
1978
+ 'aria-label': this.config.labels?.[`${method}Button`] ||
1979
+ (method === 'google'
1980
+ ? 'Continue with Google'
1981
+ : method === 'twitter'
1982
+ ? 'Continue with X'
1983
+ : `Continue with ${method.charAt(0).toUpperCase() + method.slice(1)}`),
1984
+ });
1985
+ const icon = isGoogle
1986
+ ? this.createGoogleIcon()
1987
+ : isTwitter
1988
+ ? this.createTwitterIcon()
1989
+ : this.createDiscordIcon();
1990
+ icon.setAttribute('aria-hidden', 'true');
1991
+ button.appendChild(icon);
1992
+ const label = this.config.labels?.[`${method}Button`] ||
1993
+ (method === 'google'
1994
+ ? 'Continue with Google'
1995
+ : method === 'twitter'
1996
+ ? 'Continue with X'
1997
+ : `Continue with ${method.charAt(0).toUpperCase() + method.slice(1)}`);
1998
+ button.appendChild(document.createTextNode(` ${label}`));
1999
+ if (isGoogle) {
2000
+ this.googleButton = button;
2001
+ }
2002
+ else if (isTwitter) {
2003
+ this.twitterButton = button;
2004
+ }
2005
+ else {
2006
+ this.discordButton = button;
2007
+ }
2008
+ card.appendChild(button);
2009
+ }
2010
+ else {
2011
+ this.socialGrid = this.createElement('div', { className: 'onboarding-social-grid' });
2012
+ socialMethods.forEach(({ key: method }) => {
2013
+ const isGoogle = method === 'google';
2014
+ const isTwitter = method === 'twitter';
2015
+ const isDiscord = method === 'discord';
2016
+ const button = this.createElement('button', {
2017
+ type: 'button',
2018
+ className: 'onboarding-button-social-icon',
2019
+ 'aria-label': this.config.labels?.[`${method}Button`] ||
2020
+ (method === 'google'
2021
+ ? 'Google'
2022
+ : method === 'twitter'
2023
+ ? 'X'
2024
+ : method.charAt(0).toUpperCase() + method.slice(1)),
2025
+ });
2026
+ const icon = isGoogle
2027
+ ? this.createGoogleIcon()
2028
+ : isTwitter
2029
+ ? this.createTwitterIcon()
2030
+ : this.createDiscordIcon();
2031
+ icon.setAttribute('aria-hidden', 'true');
2032
+ button.appendChild(icon);
2033
+ if (isGoogle) {
2034
+ this.googleButton = button;
2035
+ }
2036
+ else if (isTwitter) {
2037
+ this.twitterButton = button;
2038
+ }
2039
+ else {
2040
+ this.discordButton = button;
2041
+ }
2042
+ if (this.socialGrid) {
2043
+ this.socialGrid.appendChild(button);
2044
+ }
2045
+ });
2046
+ if (this.socialGrid) {
2047
+ card.appendChild(this.socialGrid);
2048
+ }
2049
+ }
2050
+ }
2051
+ // Passkey divider (only show if passkey is enabled and there are other auth methods)
2052
+ if (showPasskey && (showEmail || socialMethods.length > 0)) {
2053
+ this.passkeyDivider = this.createElement('div', { className: 'onboarding-divider' });
2054
+ const passkeyDividerText = this.createElement('span', {
2055
+ className: 'onboarding-divider-text',
2056
+ textContent: 'or',
2057
+ });
2058
+ this.passkeyDivider.appendChild(passkeyDividerText);
2059
+ card.appendChild(this.passkeyDivider);
2060
+ }
2061
+ else {
2062
+ this.passkeyDivider = null;
2063
+ }
2064
+ // Passkey login button (only show if passkey auth is enabled)
2065
+ if (showPasskey) {
2066
+ const passkeyBg = this.config.colors?.primary ||
2067
+ (this.config.theme === 'dark' ? '#4b5563' : '#ffffff');
2068
+ const passkeyBorder = this.config.colors?.border ||
2069
+ (this.config.theme === 'dark' ? '#6b7280' : '#e5e7eb');
2070
+ const passkeyTextColor = this.config.colors?.text ||
2071
+ (this.config.theme === 'dark' ? '#ffffff' : '#1f2937');
2072
+ // Set passkey link color based on theme: white for dark theme, black for light theme
2073
+ const passkeyLinkColor = this.config.theme === 'dark' ? '#ffffff' : '#1f2937';
2074
+ this.passkeyLoginButton = this.createElement('button', {
2075
+ type: 'button',
2076
+ className: 'onboarding-button onboarding-button-passkey',
2077
+ 'aria-label': this.config.labels?.passkeyLoginButton || 'Log in with passkey',
2078
+ style: `
2079
+ background-color: ${passkeyBg};
2080
+ color: ${passkeyTextColor};
2081
+ border: 1.5px solid ${passkeyBorder};
2082
+ `,
2083
+ });
2084
+ const passkeyIcon = this.createPasskeyIcon();
2085
+ passkeyIcon.setAttribute('aria-hidden', 'true');
2086
+ this.passkeyLoginButton.appendChild(passkeyIcon);
2087
+ const passkeyText = this.config.labels?.passkeyLoginButton || 'Log in with passkey';
2088
+ this.passkeyLoginButton.appendChild(document.createTextNode(` ${passkeyText}`));
2089
+ card.appendChild(this.passkeyLoginButton);
2090
+ // Passkey signup link
2091
+ const passkeySignupContainer = this.createElement('div', {
2092
+ style: 'text-align: center; margin-top: 12px;',
2093
+ });
2094
+ this.passkeySignupLink = this.createElement('a', {
2095
+ className: 'onboarding-passkey-signup-link',
2096
+ href: '#',
2097
+ textContent: this.config.labels?.passkeySignupButton || 'Sign up with passkey',
2098
+ 'aria-label': this.config.labels?.passkeySignupButton || 'Sign up with passkey',
2099
+ style: `color: ${passkeyLinkColor};`,
2100
+ });
2101
+ passkeySignupContainer.appendChild(this.passkeySignupLink);
2102
+ card.appendChild(passkeySignupContainer);
2103
+ // Passkey error element (local to passkey section)
2104
+ this.passkeyErrorElement = this.createElement('div', {
2105
+ className: 'onboarding-error',
2106
+ style: 'display: none; margin-top: 12px;',
2107
+ role: 'alert',
2108
+ 'aria-live': 'polite',
2109
+ });
2110
+ card.appendChild(this.passkeyErrorElement);
2111
+ }
2112
+ else {
2113
+ // Create passkey elements but hide them
2114
+ this.passkeyLoginButton = this.createElement('button', {
2115
+ type: 'button',
2116
+ className: 'onboarding-button onboarding-button-passkey',
2117
+ style: 'display: none;',
2118
+ });
2119
+ card.appendChild(this.passkeyLoginButton);
2120
+ this.passkeySignupLink = this.createElement('a', {
2121
+ className: 'onboarding-passkey-signup-link',
2122
+ href: '#',
2123
+ style: 'display: none;',
2124
+ });
2125
+ card.appendChild(this.passkeySignupLink);
2126
+ this.passkeyErrorElement = this.createElement('div', {
2127
+ className: 'onboarding-error',
2128
+ style: 'display: none;',
2129
+ });
2130
+ card.appendChild(this.passkeyErrorElement);
2131
+ }
2132
+ // External wallet divider (shown immediately if external wallets are enabled)
2133
+ this.externalWalletDivider = this.createElement('div', {
2134
+ className: 'onboarding-divider',
2135
+ style: this.externalWalletsEnabled ? '' : 'display: none;',
2136
+ });
2137
+ const externalWalletDividerText = this.createElement('span', {
2138
+ className: 'onboarding-divider-text',
2139
+ textContent: 'or',
2140
+ });
2141
+ this.externalWalletDivider.appendChild(externalWalletDividerText);
2142
+ card.appendChild(this.externalWalletDivider);
2143
+ // External wallet container (shown immediately if external wallets are enabled)
2144
+ this.externalWalletContainer = this.createElement('div', {
2145
+ id: 'onboarding-external-wallet-container',
2146
+ style: this.externalWalletsEnabled ? '' : 'display: none;',
2147
+ });
2148
+ card.appendChild(this.externalWalletContainer);
2149
+ // Footer (inside card, so it appears on initial login screen)
2150
+ if (this.config.showFooter) {
2151
+ const textColor = this.config.colors?.text ||
2152
+ (this.config.theme === 'dark' ? '#9ca3af' : '#6b7280');
2153
+ const brandColor = this.config.colors?.text ||
2154
+ (this.config.theme === 'dark' ? '#e5e7eb' : '#374151');
2155
+ this.footer = this.createElement('div', { className: 'onboarding-footer' });
2156
+ const footerText = this.createElement('p', {
2157
+ className: 'onboarding-footer-text',
2158
+ style: `color: ${textColor}; display: flex; align-items: center; gap: 6px; justify-content: center;`,
2159
+ });
2160
+ // "Powered by" text
2161
+ const poweredBySpan = this.createElement('span', { textContent: 'Powered by', style: `color: ${textColor};` });
2162
+ footerText.appendChild(poweredBySpan);
2163
+ // Inline brand with logo (clickable link)
2164
+ const brandContainer = this.createElement('a', {
2165
+ href: 'https://www.abstraxn.com/',
2166
+ target: '_blank',
2167
+ rel: 'noopener noreferrer',
2168
+ style: 'display: inline-flex; align-items: center; gap: 6px; text-decoration: none; cursor: pointer;',
2169
+ 'aria-label': 'Visit Abstraxn website',
2170
+ });
2171
+ // Abstraxn logo SVG
2172
+ const logoSvg = this.createElement('span', {
2173
+ innerHTML: `
2174
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none" style="width: 20px; height: 20px; display: inline-block; vertical-align: middle;">
2175
+ <path d="M47.9082 37.191L23.9541 23.553L0 9.91504L37.7436 -8.13502e-06L47.9082 37.191Z" fill="url(#paint0_linear_1242_781)"/>
2176
+ <path d="M13.5156 46.3594L10.1846 47.2344L8.99219 42.873L12.4365 42.4111L13.5156 46.3594ZM20.4268 44.5566L17.1152 45.4199L16.125 41.8818L19.5566 41.3721L20.4268 44.5566ZM26.9307 42.834L23.6152 43.6982L22.8438 40.9443L26.2744 40.4326L26.9307 42.834ZM33.5078 41.1074L30.1875 41.9727L29.6328 39.9951L33.0635 39.4844L33.5078 41.1074ZM39.998 39.4014L36.6729 40.2686L36.3359 39.0674L39.7656 38.5508L39.998 39.4014ZM10.1406 33.9434L11.6807 39.5762L8.15918 39.7568L6.46094 33.543L10.1406 33.9434ZM17.6582 34.5996L18.8916 39.1113L15.4199 39.3496L14.0078 34.3076L17.6582 34.5996ZM24.8779 35.332L25.8047 38.7227L22.3252 38.959L21.2266 35.0361L24.8779 35.332ZM32.0918 36.0723L32.7119 38.3398L29.2227 38.5723L28.4375 35.7656L32.0918 36.0723ZM43.2373 38.5488L43.1172 38.1172L47.7939 37.3516L43.2373 38.5488ZM39.3047 36.8096L39.6182 37.9561L36.1221 38.1846L35.6484 36.4961L39.3047 36.8096ZM47.7939 37.3516L43.0107 37.7939L42.8516 37.2236L47.7939 37.3516ZM47.8291 37.2988L42.7227 36.7529L42.5547 36.1523L47.8291 37.2988ZM47.8652 37.2412L42.3711 35.3486L42.2031 34.7471L47.8652 37.2412ZM38.748 34.8604L39.0664 36.0254L35.3369 35.4043L34.8594 33.6982L38.748 34.8604ZM31.0576 32.3994L31.6787 34.6709L27.959 34.0566L27.1719 31.2461L31.0576 32.3994ZM38.0811 32.6318L38.4092 33.8301L34.4893 32.3496L34.1543 31.1523C34.1005 30.9591 34.0322 30.7719 33.9531 30.5918L38.0811 32.6318ZM23.3682 29.9385L24.291 33.3164L20.5801 32.71L19.4844 28.7949L23.3682 29.9385ZM15.6641 27.4805L16.8896 31.9629L13.1875 31.3594L11.7812 26.3379L15.6641 27.4805ZM29.9463 28.4678L30.5693 30.748L26.6201 29.2812L25.8633 26.583C25.8534 26.5431 25.8417 26.5028 25.8281 26.4639L29.9463 28.4678ZM7.6875 25.0938L9.21484 30.6816L5.44727 29.959L3.75781 23.7793L7.6875 25.0938ZM21.5312 23.3623L22.4492 26.7227L18.5059 25.2627L17.4141 21.3643L21.5312 23.3623ZM13.6104 20.1602L14.8242 24.6006L10.8887 23.1387L9.49219 18.1553L13.6104 20.1602ZM5.2168 16.0654L6.72559 21.585L2.70703 19.9473L1.06055 13.8701L5.2168 16.0654Z" fill="url(#paint1_linear_1242_781)"/>
2177
+ <defs>
2178
+ <linearGradient id="paint0_linear_1242_781" x1="7.03362" y1="6.09543" x2="47.2003" y2="57.0656" gradientUnits="userSpaceOnUse">
2179
+ <stop stop-color="#F878D2"/>
2180
+ <stop offset="0.393522" stop-color="#FDCC81"/>
2181
+ <stop offset="1" stop-color="#F9A0D9"/>
2182
+ </linearGradient>
2183
+ <linearGradient id="paint1_linear_1242_781" x1="2.53739" y1="21.5136" x2="48.7969" y2="38.5742" gradientUnits="userSpaceOnUse">
2184
+ <stop stop-color="#F878D2"/>
2185
+ <stop offset="0.393522" stop-color="#FDCC81"/>
2186
+ <stop offset="1" stop-color="#F9A0D9"/>
2187
+ </linearGradient>
2188
+ </defs>
2189
+ </svg>
2190
+ `,
2191
+ style: 'display: inline-flex; align-items: center;',
2192
+ });
2193
+ brandContainer.appendChild(logoSvg);
2194
+ const brandText = this.createElement('span', {
2195
+ textContent: 'abstraxn',
2196
+ style: `color: ${brandColor}; font-weight: 700; letter-spacing: 0.2px;`,
2197
+ });
2198
+ brandContainer.appendChild(brandText);
2199
+ footerText.appendChild(brandContainer);
2200
+ this.footer.appendChild(footerText);
2201
+ card.appendChild(this.footer);
2202
+ }
2203
+ this.rootElement.appendChild(card);
2204
+ }
2205
+ /**
2206
+ * Attach event listeners
2207
+ */
2208
+ attachEventListeners() {
2209
+ if (!this.emailForm || !this.emailInput || !this.otpInput || !this.continueButton) {
2210
+ return;
2211
+ }
2212
+ // Email input change
2213
+ this.emailInput.addEventListener('input', (e) => {
2214
+ this.email = e.target.value;
2215
+ this.updateButtonState();
2216
+ });
2217
+ // OTP input change
2218
+ this.otpInput.addEventListener('input', (e) => {
2219
+ this.otp = e.target.value;
2220
+ });
2221
+ // Form submit
2222
+ this.emailForm.addEventListener('submit', (e) => {
2223
+ e.preventDefault();
2224
+ this.handleEmailSubmit();
2225
+ });
2226
+ // Google button click
2227
+ if (this.googleButton) {
2228
+ this.googleButton.addEventListener('click', (e) => {
2229
+ e.preventDefault();
2230
+ this.handleGoogleLogin();
2231
+ });
2232
+ }
2233
+ // Twitter button click
2234
+ if (this.twitterButton) {
2235
+ this.twitterButton.addEventListener('click', (e) => {
2236
+ e.preventDefault();
2237
+ this.handleTwitterLogin();
2238
+ });
2239
+ }
2240
+ // Discord button click
2241
+ if (this.discordButton) {
2242
+ this.discordButton.addEventListener('click', (e) => {
2243
+ e.preventDefault();
2244
+ this.handleDiscordLogin();
2245
+ });
2246
+ }
2247
+ // Passkey login button click
2248
+ if (this.passkeyLoginButton) {
2249
+ this.passkeyLoginButton.addEventListener('click', (e) => {
2250
+ e.preventDefault();
2251
+ this.handlePasskeyLogin();
2252
+ });
2253
+ }
2254
+ // Passkey signup link click
2255
+ if (this.passkeySignupLink) {
2256
+ this.passkeySignupLink.addEventListener('click', (e) => {
2257
+ e.preventDefault();
2258
+ this.handlePasskeySignup();
2259
+ });
2260
+ }
2261
+ }
2262
+ /**
2263
+ * Handle email form submission
2264
+ */
2265
+ async handleEmailSubmit() {
2266
+ if (!this.emailInput || !this.otpInput)
2267
+ return;
2268
+ // Ensure email is captured from input field
2269
+ this.email = this.emailInput.value.trim();
2270
+ if (!this.email || !this.email.includes('@')) {
2271
+ this.setError('Please enter a valid email address');
2272
+ return;
2273
+ }
2274
+ this.setError(null);
2275
+ this.setLoading(true, 'email');
2276
+ try {
2277
+ if (!this.otpSent) {
2278
+ // Initiate OTP
2279
+ if (this.config.onEmailOtpInitiate) {
2280
+ await this.config.onEmailOtpInitiate(this.email);
2281
+ }
2282
+ else if (this.config.emailOtpEndpoint) {
2283
+ const response = await fetch(this.config.emailOtpEndpoint, {
2284
+ method: 'POST',
2285
+ headers: {
2286
+ 'Content-Type': 'application/json',
2287
+ },
2288
+ body: JSON.stringify({ email: this.email }),
2289
+ });
2290
+ if (!response.ok) {
2291
+ throw new Error('Failed to send OTP');
2292
+ }
2293
+ }
2294
+ this.otpSent = true;
2295
+ this.showOtpInput();
2296
+ if (this.continueButton) {
2297
+ this.continueButton.textContent = 'Verify OTP';
2298
+ }
2299
+ }
2300
+ else {
2301
+ // Verify OTP
2302
+ if (this.config.onEmailOtpVerify) {
2303
+ const result = await this.config.onEmailOtpVerify(this.email, this.otp);
2304
+ if (result.success) {
2305
+ this.config.onLoginSuccess?.({ token: result.token });
2306
+ }
2307
+ else {
2308
+ throw new Error('Invalid OTP');
2309
+ }
2310
+ }
2311
+ else if (this.config.emailOtpEndpoint) {
2312
+ const response = await fetch(`${this.config.emailOtpEndpoint}/verify`, {
2313
+ method: 'POST',
2314
+ headers: {
2315
+ 'Content-Type': 'application/json',
2316
+ },
2317
+ body: JSON.stringify({ email: this.email, otp: this.otp }),
2318
+ });
2319
+ const data = await response.json();
2320
+ if (data.success) {
2321
+ this.config.onLoginSuccess?.({ token: data.token });
2322
+ }
2323
+ else {
2324
+ throw new Error(data.message || 'Invalid OTP');
2325
+ }
2326
+ }
2327
+ }
2328
+ }
2329
+ catch (err) {
2330
+ const errorMessage = err instanceof Error ? err.message : 'An error occurred';
2331
+ this.setError(errorMessage);
2332
+ this.config.onLoginError?.(err instanceof Error ? err : new Error(errorMessage));
2333
+ }
2334
+ finally {
2335
+ this.setLoading(false);
2336
+ }
2337
+ }
2338
+ /**
2339
+ * Handle Google login
2340
+ */
2341
+ async handleGoogleLogin() {
2342
+ // console.log('🔵 Google login button clicked');
2343
+ // console.log('🔵 Config:', {
2344
+ // hasOnGoogleLogin: !!this.config.onGoogleLogin,
2345
+ // googleAuthEndpoint: this.config.googleAuthEndpoint
2346
+ // });
2347
+ this.setError(null);
2348
+ this.setLoading(true, 'google');
2349
+ try {
2350
+ localStorage.setItem('abstraxn_login_source', this.config.modal ? 'modal' : 'inline');
2351
+ if (this.config.onGoogleLogin) {
2352
+ // console.log('🔄 Calling onGoogleLogin callback');
2353
+ await this.config.onGoogleLogin();
2354
+ }
2355
+ else if (this.config.googleAuthEndpoint) {
2356
+ // console.log('🔄 Redirecting to Google auth endpoint:', this.config.googleAuthEndpoint);
2357
+ window.location.href = this.config.googleAuthEndpoint;
2358
+ }
2359
+ else {
2360
+ throw new Error('Google login endpoint not configured');
2361
+ }
2362
+ }
2363
+ catch (err) {
2364
+ const errorMessage = err instanceof Error ? err.message : 'An error occurred';
2365
+ this.setError(errorMessage);
2366
+ this.config.onLoginError?.(err instanceof Error ? err : new Error(errorMessage));
2367
+ }
2368
+ finally {
2369
+ this.setLoading(false);
2370
+ }
2371
+ }
2372
+ /**
2373
+ * Handle Twitter login
2374
+ */
2375
+ async handleTwitterLogin() {
2376
+ this.setError(null);
2377
+ this.setLoading(true, 'twitter');
2378
+ try {
2379
+ if (this.config.onTwitterLogin) {
2380
+ await this.config.onTwitterLogin();
2381
+ }
2382
+ else if (this.config.twitterAuthEndpoint) {
2383
+ window.location.href = this.config.twitterAuthEndpoint;
2384
+ }
2385
+ else {
2386
+ throw new Error('X (Twitter) login not configured');
2387
+ }
2388
+ }
2389
+ catch (err) {
2390
+ const errorMessage = err instanceof Error ? err.message : 'An error occurred';
2391
+ this.setError(errorMessage);
2392
+ this.config.onLoginError?.(err instanceof Error ? err : new Error(errorMessage));
2393
+ }
2394
+ finally {
2395
+ this.setLoading(false);
2396
+ }
2397
+ }
2398
+ /**
2399
+ * Handle Discord login
2400
+ */
2401
+ async handleDiscordLogin() {
2402
+ this.setError(null);
2403
+ this.setLoading(true, 'discord');
2404
+ try {
2405
+ if (this.config.onDiscordLogin) {
2406
+ await this.config.onDiscordLogin();
2407
+ }
2408
+ else if (this.config.discordAuthEndpoint) {
2409
+ window.location.href = this.config.discordAuthEndpoint;
2410
+ }
2411
+ else {
2412
+ throw new Error('Discord login not configured');
2413
+ }
2414
+ }
2415
+ catch (err) {
2416
+ const errorMessage = err instanceof Error ? err.message : 'An error occurred';
2417
+ this.setError(errorMessage);
2418
+ this.config.onLoginError?.(err instanceof Error ? err : new Error(errorMessage));
2419
+ }
2420
+ finally {
2421
+ this.setLoading(false);
2422
+ }
2423
+ }
2424
+ /**
2425
+ * Show OTP verification screen
2426
+ */
2427
+ showOtpInput() {
2428
+ if (!this.rootElement)
2429
+ return;
2430
+ // Hide the email form and all login-related elements
2431
+ if (this.emailForm) {
2432
+ this.emailForm.style.display = 'none';
2433
+ }
2434
+ if (this.divider) {
2435
+ this.divider.style.display = 'none';
2436
+ }
2437
+ if (this.googleButton) {
2438
+ this.googleButton.style.display = 'none';
2439
+ }
2440
+ if (this.twitterButton) {
2441
+ this.twitterButton.style.display = 'none';
2442
+ }
2443
+ if (this.discordButton) {
2444
+ this.discordButton.style.display = 'none';
2445
+ }
2446
+ if (this.socialGrid) {
2447
+ this.socialGrid.style.display = 'none';
2448
+ }
2449
+ if (this.passkeyDivider) {
2450
+ this.passkeyDivider.style.display = 'none';
2451
+ }
2452
+ // Hide passkey buttons on OTP screen
2453
+ if (this.passkeyLoginButton) {
2454
+ this.passkeyLoginButton.style.display = 'none';
2455
+ }
2456
+ if (this.passkeySignupLink) {
2457
+ this.passkeySignupLink.style.display = 'none';
2458
+ }
2459
+ if (this.passkeyErrorElement) {
2460
+ this.passkeyErrorElement.style.display = 'none';
2461
+ }
2462
+ // Hide external wallets on OTP screen
2463
+ if (this.externalWalletContainer) {
2464
+ this.externalWalletContainer.style.display = 'none';
2465
+ }
2466
+ if (this.externalWalletDivider) {
2467
+ this.externalWalletDivider.style.display = 'none';
2468
+ }
2469
+ // Hide footer on login card (OTP screen has its own footer)
2470
+ if (this.footer) {
2471
+ this.footer.style.display = 'none';
2472
+ }
2473
+ // Create OTP verification screen
2474
+ this.createOtpVerificationScreen();
2475
+ }
2476
+ /**
2477
+ * Create OTP verification screen with 6 input fields
2478
+ */
2479
+ createOtpVerificationScreen() {
2480
+ if (!this.rootElement)
2481
+ return;
2482
+ // Remove existing OTP screen if any
2483
+ if (this.otpVerificationScreen) {
2484
+ this.otpVerificationScreen.remove();
2485
+ this.verifyButton = null;
2486
+ }
2487
+ // Get the card element
2488
+ const card = this.rootElement.querySelector('.onboarding-card');
2489
+ if (!card)
2490
+ return;
2491
+ // Create OTP verification container
2492
+ this.otpVerificationScreen = this.createElement('div', {
2493
+ className: 'onboarding-otp-verification',
2494
+ });
2495
+ // Email icon container
2496
+ const iconContainer = this.createElement('div', {
2497
+ className: 'onboarding-otp-icon-container',
2498
+ });
2499
+ const iconInner = this.createElement('div', {
2500
+ className: 'onboarding-otp-icon-inner',
2501
+ });
2502
+ const emailIcon = this.createEmailIconSVG();
2503
+ iconInner.appendChild(emailIcon);
2504
+ iconContainer.appendChild(iconInner);
2505
+ this.otpVerificationScreen.appendChild(iconContainer);
2506
+ // Title
2507
+ const title = this.createElement('h1', {
2508
+ className: 'onboarding-otp-title',
2509
+ textContent: 'Enter verification code',
2510
+ });
2511
+ this.otpVerificationScreen.appendChild(title);
2512
+ // Instruction text
2513
+ const instruction = this.createElement('p', {
2514
+ className: 'onboarding-otp-instruction',
2515
+ textContent: 'We sent a verification code to',
2516
+ });
2517
+ this.otpVerificationScreen.appendChild(instruction);
2518
+ // Email address
2519
+ const emailDisplay = this.createElement('p', {
2520
+ className: 'onboarding-otp-email',
2521
+ textContent: this.email,
2522
+ });
2523
+ this.otpVerificationScreen.appendChild(emailDisplay);
2524
+ // OTP inputs container
2525
+ const inputsContainer = this.createElement('div', {
2526
+ className: 'onboarding-otp-inputs-container',
2527
+ });
2528
+ // Create 6 input fields
2529
+ this.otpInputs = [];
2530
+ for (let i = 0; i < 6; i++) {
2531
+ const input = this.createElement('input', {
2532
+ type: 'text',
2533
+ className: 'onboarding-otp-input',
2534
+ maxLength: 1,
2535
+ inputMode: 'numeric',
2536
+ pattern: '[0-9]*',
2537
+ });
2538
+ // Add event listeners for auto-advance
2539
+ input.addEventListener('input', (e) => this.handleOtpInput(e, i));
2540
+ input.addEventListener('keydown', (e) => this.handleOtpKeydown(e, i));
2541
+ input.addEventListener('paste', (e) => this.handleOtpPaste(e));
2542
+ this.otpInputs.push(input);
2543
+ inputsContainer.appendChild(input);
2544
+ }
2545
+ this.otpVerificationScreen.appendChild(inputsContainer);
2546
+ // Error element (for OTP screen)
2547
+ const otpErrorElement = this.createElement('div', {
2548
+ className: 'onboarding-error',
2549
+ style: 'display: none;',
2550
+ });
2551
+ this.otpVerificationScreen.appendChild(otpErrorElement);
2552
+ // Store reference for OTP screen errors
2553
+ this.otpVerificationScreen.errorElement = otpErrorElement;
2554
+ // Verify button
2555
+ this.verifyButton = this.createElement('button', {
2556
+ type: 'button',
2557
+ className: 'onboarding-button onboarding-button-primary',
2558
+ textContent: 'Verify',
2559
+ });
2560
+ this.verifyButton.addEventListener('click', () => this.handleOtpVerify());
2561
+ this.otpVerificationScreen.appendChild(this.verifyButton);
2562
+ // Resend section
2563
+ const resendSection = this.createElement('div', {
2564
+ className: 'onboarding-otp-resend',
2565
+ });
2566
+ const resendText = this.createElement('p', {
2567
+ className: 'onboarding-otp-resend-text',
2568
+ });
2569
+ resendText.innerHTML = 'Didn\'t receive the email? <span class="onboarding-otp-resend-link" id="otp-resend-link">Resend</span>';
2570
+ resendSection.appendChild(resendText);
2571
+ this.otpVerificationScreen.appendChild(resendSection);
2572
+ // Add resend click handler
2573
+ const resendLink = resendSection.querySelector('#otp-resend-link');
2574
+ if (resendLink) {
2575
+ this.resendButton = resendLink;
2576
+ resendLink.addEventListener('click', () => this.handleResendOtp());
2577
+ }
2578
+ // Footer (add to OTP screen as well)
2579
+ if (this.config.showFooter) {
2580
+ const textColor = this.config.colors?.text ||
2581
+ (this.config.theme === 'dark' ? '#9ca3af' : '#6b7280');
2582
+ const brandColor = this.config.colors?.text ||
2583
+ (this.config.theme === 'dark' ? '#e5e7eb' : '#374151');
2584
+ const otpFooter = this.createElement('div', { className: 'onboarding-footer' });
2585
+ const otpFooterText = this.createElement('p', {
2586
+ className: 'onboarding-footer-text',
2587
+ style: `color: ${textColor}; display: flex; align-items: center; gap: 6px; justify-content: center;`,
2588
+ });
2589
+ // "Powered by" text
2590
+ const poweredBySpan = this.createElement('span', { textContent: 'Powered by', style: `color: ${textColor};` });
2591
+ otpFooterText.appendChild(poweredBySpan);
2592
+ // Inline brand with logo (clickable link)
2593
+ const brandContainer = this.createElement('a', {
2594
+ href: 'https://www.abstraxn.com/',
2595
+ target: '_blank',
2596
+ rel: 'noopener noreferrer',
2597
+ style: 'display: inline-flex; align-items: center; gap: 6px; text-decoration: none; cursor: pointer;',
2598
+ 'aria-label': 'Visit Abstraxn website',
2599
+ });
2600
+ // Abstraxn logo SVG
2601
+ const logoSvg = this.createElement('span', {
2602
+ innerHTML: `
2603
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none" style="width: 20px; height: 20px; display: inline-block; vertical-align: middle;">
2604
+ <path d="M47.9082 37.191L23.9541 23.553L0 9.91504L37.7436 -8.13502e-06L47.9082 37.191Z" fill="url(#paint0_linear_1242_781_otp)"/>
2605
+ <path d="M13.5156 46.3594L10.1846 47.2344L8.99219 42.873L12.4365 42.4111L13.5156 46.3594ZM20.4268 44.5566L17.1152 45.4199L16.125 41.8818L19.5566 41.3721L20.4268 44.5566ZM26.9307 42.834L23.6152 43.6982L22.8438 40.9443L26.2744 40.4326L26.9307 42.834ZM33.5078 41.1074L30.1875 41.9727L29.6328 39.9951L33.0635 39.4844L33.5078 41.1074ZM39.998 39.4014L36.6729 40.2686L36.3359 39.0674L39.7656 38.5508L39.998 39.4014ZM10.1406 33.9434L11.6807 39.5762L8.15918 39.7568L6.46094 33.543L10.1406 33.9434ZM17.6582 34.5996L18.8916 39.1113L15.4199 39.3496L14.0078 34.3076L17.6582 34.5996ZM24.8779 35.332L25.8047 38.7227L22.3252 38.959L21.2266 35.0361L24.8779 35.332ZM32.0918 36.0723L32.7119 38.3398L29.2227 38.5723L28.4375 35.7656L32.0918 36.0723ZM43.2373 38.5488L43.1172 38.1172L47.7939 37.3516L43.2373 38.5488ZM39.3047 36.8096L39.6182 37.9561L36.1221 38.1846L35.6484 36.4961L39.3047 36.8096ZM47.7939 37.3516L43.0107 37.7939L42.8516 37.2236L47.7939 37.3516ZM47.8291 37.2988L42.7227 36.7529L42.5547 36.1523L47.8291 37.2988ZM47.8652 37.2412L42.3711 35.3486L42.2031 34.7471L47.8652 37.2412ZM38.748 34.8604L39.0664 36.0254L35.3369 35.4043L34.8594 33.6982L38.748 34.8604ZM31.0576 32.3994L31.6787 34.6709L27.959 34.0566L27.1719 31.2461L31.0576 32.3994ZM38.0811 32.6318L38.4092 33.8301L34.4893 32.3496L34.1543 31.1523C34.1005 30.9591 34.0322 30.7719 33.9531 30.5918L38.0811 32.6318ZM23.3682 29.9385L24.291 33.3164L20.5801 32.71L19.4844 28.7949L23.3682 29.9385ZM15.6641 27.4805L16.8896 31.9629L13.1875 31.3594L11.7812 26.3379L15.6641 27.4805ZM29.9463 28.4678L30.5693 30.748L26.6201 29.2812L25.8633 26.583C25.8534 26.5431 25.8417 26.5028 25.8281 26.4639L29.9463 28.4678ZM7.6875 25.0938L9.21484 30.6816L5.44727 29.959L3.75781 23.7793L7.6875 25.0938ZM21.5312 23.3623L22.4492 26.7227L18.5059 25.2627L17.4141 21.3643L21.5312 23.3623ZM13.6104 20.1602L14.8242 24.6006L10.8887 23.1387L9.49219 18.1553L13.6104 20.1602ZM5.2168 16.0654L6.72559 21.585L2.70703 19.9473L1.06055 13.8701L5.2168 16.0654Z" fill="url(#paint1_linear_1242_781_otp)"/>
2606
+ <defs>
2607
+ <linearGradient id="paint0_linear_1242_781_otp" x1="7.03362" y1="6.09543" x2="47.2003" y2="57.0656" gradientUnits="userSpaceOnUse">
2608
+ <stop stop-color="#F878D2"/>
2609
+ <stop offset="0.393522" stop-color="#FDCC81"/>
2610
+ <stop offset="1" stop-color="#F9A0D9"/>
2611
+ </linearGradient>
2612
+ <linearGradient id="paint1_linear_1242_781_otp" x1="2.53739" y1="21.5136" x2="48.7969" y2="38.5742" gradientUnits="userSpaceOnUse">
2613
+ <stop stop-color="#F878D2"/>
2614
+ <stop offset="0.393522" stop-color="#FDCC81"/>
2615
+ <stop offset="1" stop-color="#F9A0D9"/>
2616
+ </linearGradient>
2617
+ </defs>
2618
+ </svg>
2619
+ `,
2620
+ style: 'display: inline-flex; align-items: center;',
2621
+ });
2622
+ brandContainer.appendChild(logoSvg);
2623
+ const brandText = this.createElement('span', {
2624
+ textContent: 'abstraxn',
2625
+ style: `color: ${brandColor}; font-weight: 700; letter-spacing: 0.2px;`,
2626
+ });
2627
+ brandContainer.appendChild(brandText);
2628
+ otpFooterText.appendChild(brandContainer);
2629
+ otpFooter.appendChild(otpFooterText);
2630
+ this.otpVerificationScreen.appendChild(otpFooter);
2631
+ }
2632
+ // Append to card
2633
+ card.appendChild(this.otpVerificationScreen);
2634
+ // Focus first input
2635
+ setTimeout(() => {
2636
+ if (this.otpInputs[0]) {
2637
+ this.otpInputs[0].focus();
2638
+ }
2639
+ }, 100);
2640
+ // Start resend cooldown
2641
+ this.startResendCooldown();
2642
+ }
2643
+ /**
2644
+ * Create email icon SVG
2645
+ */
2646
+ createEmailIconSVG() {
2647
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
2648
+ svg.setAttribute('class', 'onboarding-otp-icon');
2649
+ svg.setAttribute('viewBox', '0 0 24 24');
2650
+ svg.setAttribute('fill', 'none');
2651
+ svg.setAttribute('stroke', 'currentColor');
2652
+ svg.setAttribute('stroke-width', '2');
2653
+ svg.setAttribute('stroke-linecap', 'round');
2654
+ svg.setAttribute('stroke-linejoin', 'round');
2655
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2656
+ path.setAttribute('d', 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z');
2657
+ svg.appendChild(path);
2658
+ const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
2659
+ polyline.setAttribute('points', '22,6 12,13 2,6');
2660
+ svg.appendChild(polyline);
2661
+ return svg;
2662
+ }
2663
+ /**
2664
+ * Handle OTP input - auto-advance to next field
2665
+ */
2666
+ handleOtpInput(e, index) {
2667
+ const input = e.target;
2668
+ const value = input.value.replace(/[^0-9]/g, '');
2669
+ if (value) {
2670
+ input.value = value;
2671
+ // Move to next input if available
2672
+ if (index < 5 && this.otpInputs[index + 1]) {
2673
+ this.otpInputs[index + 1].focus();
2674
+ }
2675
+ // Don't auto-verify, wait for user to click Verify button
2676
+ }
2677
+ else {
2678
+ input.value = '';
2679
+ }
2680
+ this.updateOtpValue();
2681
+ }
2682
+ /**
2683
+ * Handle OTP keydown - backspace to go to previous field
2684
+ */
2685
+ handleOtpKeydown(e, index) {
2686
+ const input = e.target;
2687
+ if (e.key === 'Backspace' && !input.value && index > 0) {
2688
+ this.otpInputs[index - 1].focus();
2689
+ }
2690
+ }
2691
+ /**
2692
+ * Handle OTP paste - fill all fields from clipboard
2693
+ */
2694
+ handleOtpPaste(e) {
2695
+ e.preventDefault();
2696
+ const pastedData = e.clipboardData?.getData('text') || '';
2697
+ const digits = pastedData.replace(/[^0-9]/g, '').slice(0, 6);
2698
+ for (let i = 0; i < 6; i++) {
2699
+ if (this.otpInputs[i]) {
2700
+ this.otpInputs[i].value = digits[i] || '';
2701
+ }
2702
+ }
2703
+ // Focus the last filled input or the last input
2704
+ const lastFilledIndex = Math.min(digits.length - 1, 5);
2705
+ if (this.otpInputs[lastFilledIndex]) {
2706
+ this.otpInputs[lastFilledIndex].focus();
2707
+ }
2708
+ this.updateOtpValue();
2709
+ // Don't auto-verify, wait for user to click Verify button
2710
+ }
2711
+ /**
2712
+ * Update OTP value from input fields
2713
+ */
2714
+ updateOtpValue() {
2715
+ this.otp = this.otpInputs.map(input => input.value).join('');
2716
+ }
2717
+ /**
2718
+ * Handle OTP verification
2719
+ */
2720
+ async handleOtpVerify() {
2721
+ this.updateOtpValue();
2722
+ if (this.otp.length !== 6) {
2723
+ this.setError('Please enter the complete 6-digit code');
2724
+ return;
2725
+ }
2726
+ // Disable inputs during verification
2727
+ this.otpInputs.forEach(input => {
2728
+ input.disabled = true;
2729
+ });
2730
+ this.setLoading(true, 'email');
2731
+ this.setError(null);
2732
+ try {
2733
+ if (this.config.onEmailOtpVerify) {
2734
+ const result = await this.config.onEmailOtpVerify(this.email, this.otp);
2735
+ if (result.success) {
2736
+ this.config.onLoginSuccess?.(result);
2737
+ }
2738
+ else {
2739
+ throw new Error(result.error || 'Invalid verification code');
2740
+ }
2741
+ }
2742
+ else if (this.config.emailOtpEndpoint) {
2743
+ const response = await fetch(`${this.config.emailOtpEndpoint}/verify`, {
2744
+ method: 'POST',
2745
+ headers: {
2746
+ 'Content-Type': 'application/json',
2747
+ },
2748
+ body: JSON.stringify({ email: this.email, otp: this.otp }),
2749
+ });
2750
+ const data = await response.json();
2751
+ if (data.success) {
2752
+ this.config.onLoginSuccess?.({ token: data.token });
2753
+ }
2754
+ else {
2755
+ throw new Error(data.message || 'Invalid verification code');
2756
+ }
2757
+ }
2758
+ }
2759
+ catch (err) {
2760
+ const errorMessage = err instanceof Error ? err.message : 'An error occurred';
2761
+ this.setError(errorMessage);
2762
+ this.config.onLoginError?.(err instanceof Error ? err : new Error(errorMessage));
2763
+ // Clear OTP inputs on error
2764
+ this.otpInputs.forEach(input => {
2765
+ input.value = '';
2766
+ input.disabled = false;
2767
+ });
2768
+ this.otp = '';
2769
+ if (this.otpInputs[0]) {
2770
+ this.otpInputs[0].focus();
2771
+ }
2772
+ }
2773
+ finally {
2774
+ this.setLoading(false);
2775
+ }
2776
+ }
2777
+ /**
2778
+ * Handle resend OTP
2779
+ */
2780
+ async handleResendOtp() {
2781
+ if (this.resendCooldown > 0) {
2782
+ return;
2783
+ }
2784
+ this.setError(null);
2785
+ this.setLoading(true, 'email');
2786
+ try {
2787
+ if (this.config.onEmailOtpInitiate) {
2788
+ await this.config.onEmailOtpInitiate(this.email);
2789
+ }
2790
+ else if (this.config.emailOtpEndpoint) {
2791
+ const response = await fetch(this.config.emailOtpEndpoint, {
2792
+ method: 'POST',
2793
+ headers: {
2794
+ 'Content-Type': 'application/json',
2795
+ },
2796
+ body: JSON.stringify({ email: this.email }),
2797
+ });
2798
+ if (!response.ok) {
2799
+ throw new Error('Failed to resend OTP');
2800
+ }
2801
+ }
2802
+ // Clear OTP inputs
2803
+ this.otpInputs.forEach(input => {
2804
+ input.value = '';
2805
+ input.disabled = false;
2806
+ });
2807
+ this.otp = '';
2808
+ if (this.otpInputs[0]) {
2809
+ this.otpInputs[0].focus();
2810
+ }
2811
+ // Start cooldown
2812
+ this.startResendCooldown();
2813
+ }
2814
+ catch (err) {
2815
+ const errorMessage = err instanceof Error ? err.message : 'Failed to resend OTP';
2816
+ this.setError(errorMessage);
2817
+ this.config.onLoginError?.(err instanceof Error ? err : new Error(errorMessage));
2818
+ }
2819
+ finally {
2820
+ this.setLoading(false);
2821
+ }
2822
+ }
2823
+ /**
2824
+ * Start resend cooldown timer
2825
+ */
2826
+ startResendCooldown() {
2827
+ this.resendCooldown = 60; // 60 seconds cooldown
2828
+ const updateCooldown = () => {
2829
+ if (this.resendCooldown > 0 && this.resendButton) {
2830
+ this.resendButton.textContent = `Resend (${this.resendCooldown}s)`;
2831
+ this.resendButton.setAttribute('disabled', 'true');
2832
+ this.resendCooldown--;
2833
+ setTimeout(updateCooldown, 1000);
2834
+ }
2835
+ else if (this.resendButton) {
2836
+ this.resendButton.textContent = 'Resend';
2837
+ this.resendButton.removeAttribute('disabled');
2838
+ }
2839
+ };
2840
+ updateCooldown();
2841
+ }
2842
+ /**
2843
+ * Set error message
2844
+ */
2845
+ setError(message) {
2846
+ // Show error in OTP screen if it exists
2847
+ if (this.otpVerificationScreen) {
2848
+ const otpErrorElement = this.otpVerificationScreen.errorElement;
2849
+ if (otpErrorElement) {
2850
+ if (message) {
2851
+ otpErrorElement.textContent = message;
2852
+ otpErrorElement.style.display = 'block';
2853
+ }
2854
+ else {
2855
+ otpErrorElement.style.display = 'none';
2856
+ }
2857
+ return;
2858
+ }
2859
+ }
2860
+ // Fallback to regular error element
2861
+ if (!this.errorElement)
2862
+ return;
2863
+ if (message) {
2864
+ this.errorElement.textContent = message;
2865
+ this.errorElement.style.display = 'block';
2866
+ }
2867
+ else {
2868
+ this.errorElement.style.display = 'none';
2869
+ }
2870
+ }
2871
+ /**
2872
+ * Set loading state
2873
+ */
2874
+ setLoading(loading, buttonType) {
2875
+ this.loading = loading;
2876
+ // Track which button is active
2877
+ if (loading && buttonType) {
2878
+ this.activeButton = buttonType;
2879
+ }
2880
+ else if (!loading) {
2881
+ this.activeButton = null;
2882
+ }
2883
+ // Email/Continue button
2884
+ if (this.continueButton) {
2885
+ this.continueButton.disabled = loading || !this.email;
2886
+ // Show loading text only if this is the active button
2887
+ if (loading && this.activeButton === 'email') {
2888
+ this.continueButton.textContent = this.otpSent ? 'Verifying...' : 'Loading...';
2889
+ }
2890
+ else if (!loading) {
2891
+ this.continueButton.textContent = this.otpSent ? 'Verify OTP' : 'Continue';
2892
+ }
2893
+ }
2894
+ // Email arrow button (when all auth methods are enabled)
2895
+ if (this.emailArrowButton) {
2896
+ this.emailArrowButton.disabled = loading || !this.email;
2897
+ }
2898
+ // Google button
2899
+ if (this.googleButton) {
2900
+ this.googleButton.disabled = loading;
2901
+ // Google button doesn't change text, just gets disabled
2902
+ }
2903
+ // Twitter button
2904
+ if (this.twitterButton) {
2905
+ this.twitterButton.disabled = loading;
2906
+ }
2907
+ // Discord button
2908
+ if (this.discordButton) {
2909
+ this.discordButton.disabled = loading;
2910
+ }
2911
+ // Passkey login button
2912
+ if (this.passkeyLoginButton) {
2913
+ this.passkeyLoginButton.disabled = loading;
2914
+ // Show loading text only if this is the active button
2915
+ if (loading && this.activeButton === 'passkey') {
2916
+ // Update only the text node, preserve the icon
2917
+ const textNodes = Array.from(this.passkeyLoginButton.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
2918
+ if (textNodes.length > 0) {
2919
+ textNodes[0].textContent = ' Authenticating...';
2920
+ }
2921
+ else {
2922
+ // If no text node exists, append one (icon should already be there)
2923
+ this.passkeyLoginButton.appendChild(document.createTextNode(' Authenticating...'));
2924
+ }
2925
+ this.passkeyLoginButton.style.opacity = '0.7';
2926
+ this.passkeyLoginButton.style.cursor = 'not-allowed';
2927
+ }
2928
+ else if (!loading) {
2929
+ // Update only the text node, preserve the icon
2930
+ const textNodes = Array.from(this.passkeyLoginButton.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
2931
+ const passkeyText = ` ${this.config.labels?.passkeyLoginButton || 'Log in with passkey'}`;
2932
+ if (textNodes.length > 0) {
2933
+ textNodes[0].textContent = passkeyText;
2934
+ }
2935
+ else {
2936
+ // If no text node exists, append one (icon should already be there)
2937
+ this.passkeyLoginButton.appendChild(document.createTextNode(passkeyText));
2938
+ }
2939
+ this.passkeyLoginButton.style.opacity = '1';
2940
+ this.passkeyLoginButton.style.cursor = 'pointer';
2941
+ }
2942
+ }
2943
+ // Passkey signup link
2944
+ if (this.passkeySignupLink) {
2945
+ if (loading) {
2946
+ this.passkeySignupLink.style.opacity = '0.5';
2947
+ this.passkeySignupLink.style.pointerEvents = 'none';
2948
+ this.passkeySignupLink.style.cursor = 'not-allowed';
2949
+ }
2950
+ else {
2951
+ this.passkeySignupLink.style.opacity = '1';
2952
+ this.passkeySignupLink.style.pointerEvents = 'auto';
2953
+ this.passkeySignupLink.style.cursor = 'pointer';
2954
+ }
2955
+ }
2956
+ // Email input
2957
+ if (this.emailInput) {
2958
+ this.emailInput.disabled = loading || this.otpSent;
2959
+ }
2960
+ // OTP input
2961
+ if (this.otpInput) {
2962
+ this.otpInput.disabled = loading;
2963
+ }
2964
+ // Verify button
2965
+ if (this.verifyButton) {
2966
+ this.verifyButton.disabled = loading;
2967
+ if (loading) {
2968
+ this.verifyButton.textContent = 'Verifying...';
2969
+ this.verifyButton.style.opacity = '0.7';
2970
+ this.verifyButton.style.cursor = 'not-allowed';
2971
+ }
2972
+ else {
2973
+ this.verifyButton.textContent = 'Verify';
2974
+ this.verifyButton.style.opacity = '1';
2975
+ this.verifyButton.style.cursor = 'pointer';
2976
+ }
2977
+ }
2978
+ }
2979
+ /**
2980
+ * Update button state
2981
+ */
2982
+ updateButtonState() {
2983
+ if (this.continueButton) {
2984
+ this.continueButton.disabled = this.loading || !this.email;
2985
+ }
2986
+ // Update arrow button state (when all auth methods are enabled)
2987
+ if (this.emailArrowButton) {
2988
+ this.emailArrowButton.disabled = this.loading || !this.email;
2989
+ }
2990
+ }
2991
+ /**
2992
+ * Update theme
2993
+ */
2994
+ setTheme(theme) {
2995
+ this.config.theme = theme;
2996
+ if (this.rootElement) {
2997
+ this.rootElement.className = `onboarding-container onboarding-theme-${theme} ${this.config.className}`;
2998
+ }
2999
+ }
3000
+ /**
3001
+ * Show/hide component
3002
+ */
3003
+ setVisible(visible) {
3004
+ if (this.rootElement) {
3005
+ this.rootElement.style.display = visible ? '' : 'none';
3006
+ }
3007
+ }
3008
+ /**
3009
+ * Reset OTP screen and show initial login form
3010
+ */
3011
+ resetToLoginForm() {
3012
+ // Clear OTP verification screen
3013
+ if (this.otpVerificationScreen) {
3014
+ this.otpVerificationScreen.remove();
3015
+ this.otpVerificationScreen = null;
3016
+ }
3017
+ // Remove loading/error mode classes
3018
+ const card = this.rootElement?.querySelector('.onboarding-card');
3019
+ if (card) {
3020
+ card.classList.remove('onboarding-mode-loading');
3021
+ card.classList.remove('onboarding-mode-error');
3022
+ }
3023
+ // Hide loading modal if exists (for inline components)
3024
+ this.hideLoadingModal();
3025
+ // Remove error container if exists
3026
+ const errorContainer = this.rootElement?.querySelector('.onboarding-error-container');
3027
+ if (errorContainer) {
3028
+ errorContainer.remove();
3029
+ }
3030
+ // Clear OTP inputs
3031
+ this.otpInputs.forEach(input => {
3032
+ input.value = '';
3033
+ input.disabled = false;
3034
+ });
3035
+ this.otpInputs = [];
3036
+ this.otp = '';
3037
+ this.otpSent = false;
3038
+ this.loading = false;
3039
+ // Clear timer
3040
+ this.resendCooldown = 0;
3041
+ if (this.resendButton) {
3042
+ this.resendButton.textContent = 'Resend';
3043
+ this.resendButton.removeAttribute('disabled');
3044
+ }
3045
+ // Get auth methods
3046
+ const authMethods = this.config.authMethods || ['otp', 'google'];
3047
+ const showEmail = authMethods.includes('otp');
3048
+ const showGoogle = authMethods.includes('google');
3049
+ const showTwitter = authMethods.includes('twitter');
3050
+ const showDiscord = authMethods.includes('discord');
3051
+ const showPasskey = authMethods.includes('passkey');
3052
+ const socialMethods = [
3053
+ { key: 'google', show: showGoogle },
3054
+ { key: 'twitter', show: showTwitter },
3055
+ { key: 'discord', show: showDiscord },
3056
+ ].filter((m) => m.show);
3057
+ // Show the email form and all login-related elements
3058
+ if (this.emailForm) {
3059
+ this.emailForm.style.display = showEmail ? '' : 'none';
3060
+ }
3061
+ if (this.divider) {
3062
+ this.divider.style.display = (showEmail && socialMethods.length > 0) ? '' : 'none';
3063
+ }
3064
+ if (this.passkeyDivider) {
3065
+ this.passkeyDivider.style.display = (showPasskey && (showEmail || socialMethods.length > 0)) ? '' : 'none';
3066
+ }
3067
+ if (this.googleButton) {
3068
+ this.googleButton.style.display = showGoogle ? '' : 'none';
3069
+ this.googleButton.disabled = false;
3070
+ }
3071
+ if (this.twitterButton) {
3072
+ this.twitterButton.style.display = showTwitter ? '' : 'none';
3073
+ this.twitterButton.disabled = false;
3074
+ }
3075
+ if (this.discordButton) {
3076
+ this.discordButton.style.display = showDiscord ? '' : 'none';
3077
+ this.discordButton.disabled = false;
3078
+ }
3079
+ if (this.socialGrid) {
3080
+ this.socialGrid.style.display = socialMethods.length > 1 ? '' : 'none';
3081
+ }
3082
+ // Show passkey buttons again when returning to login form
3083
+ if (this.passkeyLoginButton) {
3084
+ this.passkeyLoginButton.style.display = showPasskey ? '' : 'none';
3085
+ }
3086
+ if (this.passkeySignupLink) {
3087
+ this.passkeySignupLink.style.display = showPasskey ? '' : 'none';
3088
+ }
3089
+ if (this.passkeyErrorElement) {
3090
+ this.passkeyErrorElement.style.display = 'none'; // Hide error when resetting
3091
+ }
3092
+ // Show external wallets again when returning to login form
3093
+ // Note: The visibility will be managed by AbstraxnProvider, but we ensure they're available
3094
+ // AbstraxnProvider will handle showing them if external wallets are enabled
3095
+ if (this.footer) {
3096
+ this.footer.style.display = '';
3097
+ }
3098
+ // Clear error messages
3099
+ this.setError(null);
3100
+ if (this.errorElement) {
3101
+ this.errorElement.style.display = 'none';
3102
+ }
3103
+ // Reset email input - enable it and clear value
3104
+ if (this.emailInput) {
3105
+ this.emailInput.value = '';
3106
+ this.emailInput.disabled = false;
3107
+ this.email = '';
3108
+ }
3109
+ // Reset OTP group (hidden input)
3110
+ if (this.otpGroup) {
3111
+ this.otpGroup.style.display = 'none';
3112
+ if (this.otpInput) {
3113
+ this.otpInput.value = '';
3114
+ this.otpInput.disabled = false;
3115
+ }
3116
+ }
3117
+ // Reset button state - ensure it shows "Continue" and is properly enabled/disabled
3118
+ if (this.continueButton) {
3119
+ this.continueButton.disabled = !this.email; // Disabled only if no email
3120
+ this.continueButton.textContent = this.config.labels?.emailButton || 'Continue';
3121
+ }
3122
+ // Update button state to ensure everything is in sync
3123
+ this.updateButtonState();
3124
+ }
3125
+ /**
3126
+ * Close the modal
3127
+ */
3128
+ close() {
3129
+ // Clear URL params to prevent showing error again on refresh
3130
+ const url = new URL(window.location.href);
3131
+ url.searchParams.delete('error');
3132
+ url.searchParams.delete('success');
3133
+ window.history.replaceState({}, document.title, url.toString());
3134
+ if (this.modalOverlay) {
3135
+ // Add closing animation
3136
+ this.modalOverlay.classList.remove('onboarding-modal-open');
3137
+ this.modalOverlay.classList.add('onboarding-modal-closing');
3138
+ // Remove after animation
3139
+ setTimeout(() => {
3140
+ this.destroy();
3141
+ }, 200);
3142
+ }
3143
+ else {
3144
+ this.destroy();
3145
+ }
3146
+ // Restore body scroll
3147
+ document.body.style.overflow = '';
3148
+ // Call onClose callback
3149
+ if (this.config.onClose) {
3150
+ this.config.onClose();
3151
+ }
3152
+ }
3153
+ /**
3154
+ * Destroy the component
3155
+ */
3156
+ destroy() {
3157
+ // Reset to login form before destroying to ensure clean state
3158
+ this.resetToLoginForm();
3159
+ // Clean up modal
3160
+ if (this.modalOverlay && this.modalOverlay.parentNode) {
3161
+ this.modalOverlay.parentNode.removeChild(this.modalOverlay);
3162
+ }
3163
+ // Clean up inline
3164
+ if (this.rootElement && this.rootElement.parentNode) {
3165
+ this.rootElement.parentNode.removeChild(this.rootElement);
3166
+ }
3167
+ // Restore body scroll
3168
+ document.body.style.overflow = '';
3169
+ // Clear references
3170
+ this.rootElement = null;
3171
+ this.modalOverlay = null;
3172
+ this.modalContent = null;
3173
+ this.emailInput = null;
3174
+ this.otpInput = null;
3175
+ this.emailForm = null;
3176
+ this.continueButton = null;
3177
+ this.googleButton = null;
3178
+ this.errorElement = null;
3179
+ this.otpGroup = null;
3180
+ this.closeButton = null;
3181
+ this.otpVerificationScreen = null; // Clear OTP screen reference
3182
+ this.externalWalletContainer = null; // Clear external wallet container reference
3183
+ this.externalWalletDivider = null; // Clear external wallet divider reference
3184
+ }
3185
+ /**
3186
+ * Create DOM element helper
3187
+ */
3188
+ createElement(tag, options = {}) {
3189
+ const element = document.createElement(tag);
3190
+ Object.keys(options).forEach((key) => {
3191
+ const value = options[key];
3192
+ if (key === 'className' && typeof value === 'string') {
3193
+ element.className = value;
3194
+ }
3195
+ else if (key === 'textContent' && (typeof value === 'string' || value === null)) {
3196
+ element.textContent = value;
3197
+ }
3198
+ else if (key === 'innerHTML' && typeof value === 'string') {
3199
+ element.innerHTML = value;
3200
+ }
3201
+ else if (key === 'style' && typeof value === 'string') {
3202
+ element.setAttribute('style', value);
3203
+ }
3204
+ else if (key === 'htmlFor' && typeof value === 'string') {
3205
+ // Use setAttribute for htmlFor to ensure it works correctly
3206
+ element.setAttribute('for', value);
3207
+ }
3208
+ else if (key.startsWith('aria-') || key === 'role' || key === 'tabIndex' || key === 'autocomplete' || key === 'noValidate') {
3209
+ // Handle ARIA attributes and other special attributes
3210
+ if (key === 'tabIndex') {
3211
+ element.tabIndex = value;
3212
+ }
3213
+ else if (key === 'noValidate') {
3214
+ element.noValidate = value;
3215
+ }
3216
+ else {
3217
+ element.setAttribute(key, String(value));
3218
+ }
3219
+ }
3220
+ else if (key !== 'style' && value !== undefined) {
3221
+ element[key] = value;
3222
+ }
3223
+ });
3224
+ return element;
3225
+ }
3226
+ /**
3227
+ * Parse style object or string
3228
+ */
3229
+ parseStyle(style) {
3230
+ if (!style)
3231
+ return '';
3232
+ if (typeof style === 'string')
3233
+ return style;
3234
+ return Object.entries(style)
3235
+ .filter(([_, value]) => value !== undefined && value !== null)
3236
+ .map(([key, value]) => {
3237
+ const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
3238
+ return `${cssKey}: ${value}`;
3239
+ })
3240
+ .join('; ');
3241
+ }
3242
+ /**
3243
+ * Create email icon SVG
3244
+ */
3245
+ createEmailIcon() {
3246
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
3247
+ svg.setAttribute('class', 'onboarding-input-icon');
3248
+ svg.setAttribute('width', '20');
3249
+ svg.setAttribute('height', '20');
3250
+ svg.setAttribute('viewBox', '0 0 20 20');
3251
+ svg.setAttribute('fill', 'none');
3252
+ svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
3253
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
3254
+ path.setAttribute('d', 'M2.5 6.66667L10 11.6667L17.5 6.66667M3.33333 15H16.6667C17.5871 15 18.3333 14.2538 18.3333 13.3333V6.66667C18.3333 5.74619 17.5871 5 16.6667 5H3.33333C2.41286 5 1.66667 5.74619 1.66667 6.66667V13.3333C1.66667 14.2538 2.41286 15 3.33333 15Z');
3255
+ path.setAttribute('stroke', 'currentColor');
3256
+ path.setAttribute('stroke-width', '1.5');
3257
+ path.setAttribute('stroke-linecap', 'round');
3258
+ path.setAttribute('stroke-linejoin', 'round');
3259
+ svg.appendChild(path);
3260
+ return svg;
3261
+ }
3262
+ /**
3263
+ * Create Google icon SVG
3264
+ */
3265
+ createGoogleIcon() {
3266
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
3267
+ svg.setAttribute('class', 'onboarding-google-icon');
3268
+ svg.setAttribute('width', '20');
3269
+ svg.setAttribute('height', '20');
3270
+ svg.setAttribute('viewBox', '0 0 20 20');
3271
+ svg.setAttribute('fill', 'none');
3272
+ svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
3273
+ const paths = [
3274
+ {
3275
+ d: 'M18.1712 8.36792H17.5V8.33333H10V11.6667H14.7096C14.2225 13.1071 13.2408 14.2854 12.0125 15.0125L14.9696 17.3458C16.7792 15.6958 17.9167 13.3958 17.9167 10.8333C17.9167 10.275 17.8708 9.72917 17.7833 9.20417L18.1712 8.36792Z',
3276
+ fill: '#4285F4',
3277
+ },
3278
+ {
3279
+ d: 'M10 18.3333C12.5542 18.3333 14.7208 17.4708 16.3042 16.0125L13.3471 13.6792C12.5042 14.2542 11.4042 14.5833 10 14.5833C7.52083 14.5833 5.39583 12.9042 4.61667 10.6917L1.57083 13.0292C3.13333 16.1 6.32083 18.3333 10 18.3333Z',
3280
+ fill: '#34A853',
3281
+ },
3282
+ {
3283
+ d: 'M4.61667 10.6917C4.39583 10.0708 4.27083 9.40417 4.27083 8.70833C4.27083 8.0125 4.39583 7.34583 4.61667 6.72417L1.57083 4.3875C0.9375 5.66667 0.583336 7.10833 0.583336 8.70833C0.583336 10.3083 0.9375 11.75 1.57083 13.0292L4.61667 10.6917Z',
3284
+ fill: '#FBBC05',
3285
+ },
3286
+ {
3287
+ d: 'M10 2.91667C11.5125 2.91667 12.8708 3.47083 13.9292 4.42917L16.375 2C14.7208 0.483333 12.5542 -0.000165871 10 -0.000165871C6.32083 -0.000165871 3.13333 2.23333 1.57083 5.30417L4.61667 7.64167C5.39583 5.42917 7.52083 3.75 10 3.75V2.91667Z',
3288
+ fill: '#EA4335',
3289
+ },
3290
+ ];
3291
+ paths.forEach((pathData) => {
3292
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
3293
+ path.setAttribute('d', pathData.d);
3294
+ path.setAttribute('fill', pathData.fill);
3295
+ svg.appendChild(path);
3296
+ });
3297
+ return svg;
3298
+ }
3299
+ /**
3300
+ * Create right arrow icon SVG
3301
+ */
3302
+ createArrowIcon() {
3303
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
3304
+ svg.setAttribute('class', 'onboarding-input-arrow-icon');
3305
+ svg.setAttribute('width', '20');
3306
+ svg.setAttribute('height', '20');
3307
+ svg.setAttribute('viewBox', '0 0 20 20');
3308
+ svg.setAttribute('fill', 'none');
3309
+ svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
3310
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
3311
+ path.setAttribute('d', 'M7.5 15L12.5 10L7.5 5');
3312
+ path.setAttribute('stroke', 'currentColor');
3313
+ path.setAttribute('stroke-width', '2');
3314
+ path.setAttribute('stroke-linecap', 'round');
3315
+ path.setAttribute('stroke-linejoin', 'round');
3316
+ svg.appendChild(path);
3317
+ return svg;
3318
+ }
3319
+ /**
3320
+ * Create passkey icon SVG
3321
+ */
3322
+ createPasskeyIcon() {
3323
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
3324
+ svg.setAttribute('class', 'onboarding-passkey-icon');
3325
+ svg.setAttribute('width', '20');
3326
+ svg.setAttribute('height', '20');
3327
+ svg.setAttribute('viewBox', '0 0 24 24');
3328
+ svg.setAttribute('fill', 'none');
3329
+ svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
3330
+ svg.setAttribute('aria-hidden', 'true');
3331
+ // Passkey icon (user with key)
3332
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
3333
+ path.setAttribute('d', 'M3.682 19.338v-2.102c0-.476.127-.915.382-1.315.255-.4.603-.708 1.042-.926a14.89 14.89 0 0 1 2.939-1.067c.982-.24 1.966-.36 2.953-.36.352 0 .708.018 1.066.054.358.035.712.084 1.062.147-.015.831.164 1.613.537 2.347a4.882 4.882 0 0 0 1.595 1.822v1.4H3.682Zm15.003 2.713-1.303-1.303v-4.029a2.976 2.976 0 0 1-1.564-1.08 2.944 2.944 0 0 1-.608-1.832c0-.841.297-1.557.891-2.147a2.944 2.944 0 0 1 2.154-.886c.837 0 1.552.295 2.145.886.594.59.89 1.306.89 2.148 0 .656-.182 1.238-.546 1.743a3.018 3.018 0 0 1-1.404 1.084l1.073 1.072-1.294 1.31 1.294 1.301-1.728 1.733ZM11 12.006c-.94 0-1.742-.333-2.405-.999a3.28 3.28 0 0 1-.994-2.4c0-.94.331-1.743.994-2.405A3.276 3.276 0 0 1 11 5.208c.94 0 1.742.331 2.405.994.663.662.994 1.463.994 2.4 0 .939-.331 1.74-.994 2.406a3.27 3.27 0 0 1-2.405.998Zm7.252 2.235a.833.833 0 0 0 .612-.255.842.842 0 0 0 .255-.617.834.834 0 0 0-.253-.614.835.835 0 0 0-.612-.252.85.85 0 0 0-.616.251.823.823 0 0 0-.257.612c0 .24.085.447.257.618a.838.838 0 0 0 .614.257Z');
3334
+ // Set icon color based on theme: white for dark theme, black for light theme
3335
+ const iconColor = this.config.theme === 'dark' ? '#ffffff' : '#000000';
3336
+ path.setAttribute('fill', iconColor);
3337
+ svg.appendChild(path);
3338
+ return svg;
3339
+ }
3340
+ /**
3341
+ * Create X (Twitter) icon SVG
3342
+ */
3343
+ createTwitterIcon() {
3344
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
3345
+ svg.setAttribute('class', 'onboarding-social-icon');
3346
+ svg.setAttribute('width', '20');
3347
+ svg.setAttribute('height', '20');
3348
+ svg.setAttribute('viewBox', '0 0 24 24');
3349
+ svg.setAttribute('fill', 'none');
3350
+ svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
3351
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
3352
+ path.setAttribute('d', 'M4 3h4.6l3.09 4.1L14.5 3H20l-5.63 7.08L20.4 21H15.8l-3.5-4.65L8.3 21H2.8l6-7.67L4 3Zm2.04 1.2L9.7 9l.1.12L4.97 19h1.79l4.04-5.39L14.78 19h1.8l-5.02-6.55-.05-.06L17.97 4.2h-1.8l-3.52 4.62L9 4.2H6.04Z');
3353
+ path.setAttribute('fill', 'currentColor');
3354
+ svg.appendChild(path);
3355
+ return svg;
3356
+ }
3357
+ /**
3358
+ * Create Discord icon SVG
3359
+ */
3360
+ createDiscordIcon() {
3361
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
3362
+ svg.setAttribute('class', 'onboarding-social-icon');
3363
+ svg.setAttribute('width', '20');
3364
+ svg.setAttribute('height', '20');
3365
+ svg.setAttribute('viewBox', '0 0 24 24');
3366
+ svg.setAttribute('fill', 'none');
3367
+ svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
3368
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
3369
+ path.setAttribute('d', 'M20.317 4.37a16.75 16.75 0 0 0-4.091-1.272.06.06 0 0 0-.063.03 11.651 11.651 0 0 0-.512 1.054 15.93 15.93 0 0 0-4.813 0 10.69 10.69 0 0 0-.522-1.054.06.06 0 0 0-.063-.03 16.742 16.742 0 0 0-4.092 1.272.055.055 0 0 0-.025.021C2.84 8.167 2.18 11.875 2.46 15.547a.066.066 0 0 0 .025.045 16.86 16.86 0 0 0 4.98 2.52.06.06 0 0 0 .066-.02c.384-.525.726-1.08 1.02-1.66a.06.06 0 0 0-.033-.083 11.117 11.117 0 0 1-1.593-.765.06.06 0 0 1-.006-.1c.107-.08.214-.162.316-.245a.06.06 0 0 1 .061-.008 11.65 11.65 0 0 0 10.104 0 .06.06 0 0 1 .062.007c.103.083.21.166.316.246a.06.06 0 0 1-.005.1 10.859 10.859 0 0 1-1.594.764.06.06 0 0 0-.033.084 9.255 9.255 0 0 0 1.02 1.659.06.06 0 0 0 .065.02 16.824 16.824 0 0 0 4.982-2.52.06.06 0 0 0 .024-.044c.416-5.09-.698-8.76-2.89-11.156a.048.048 0 0 0-.024-.02ZM9.68 13.348c-.996 0-1.812-.91-1.812-2.03 0-1.12.805-2.031 1.812-2.031 1.016 0 1.822.922 1.812 2.031 0 1.12-.805 2.03-1.812 2.03Zm4.64 0c-.996 0-1.812-.91-1.812-2.03 0-1.12.805-2.031 1.812-2.031 1.016 0 1.822.922 1.812 2.031 0 1.12-.796 2.03-1.812 2.03Z');
3370
+ path.setAttribute('fill', 'currentColor');
3371
+ svg.appendChild(path);
3372
+ return svg;
3373
+ }
3374
+ /**
3375
+ * Handle passkey login
3376
+ */
3377
+ async handlePasskeyLogin() {
3378
+ if (this.loading)
3379
+ return;
3380
+ try {
3381
+ this.setLoading(true, 'passkey');
3382
+ this.hidePasskeyError();
3383
+ if (this.config.onPasskeyLogin) {
3384
+ await this.config.onPasskeyLogin();
3385
+ // Call onLoginSuccess to close modal and update state
3386
+ if (this.config.onLoginSuccess) {
3387
+ this.config.onLoginSuccess({ user: null });
3388
+ }
3389
+ }
3390
+ else {
3391
+ throw new Error('Passkey login handler not configured');
3392
+ }
3393
+ }
3394
+ catch (error) {
3395
+ this.showPasskeyError(error instanceof Error ? error.message : 'Failed to login with passkey');
3396
+ if (this.config.onLoginError) {
3397
+ this.config.onLoginError(error instanceof Error ? error : new Error('Failed to login with passkey'));
3398
+ }
3399
+ }
3400
+ finally {
3401
+ this.setLoading(false);
3402
+ }
3403
+ }
3404
+ /**
3405
+ * Handle passkey signup
3406
+ */
3407
+ async handlePasskeySignup() {
3408
+ if (this.loading)
3409
+ return;
3410
+ try {
3411
+ this.setLoading(true, 'passkey');
3412
+ this.hidePasskeyError();
3413
+ if (this.config.onPasskeySignup) {
3414
+ await this.config.onPasskeySignup();
3415
+ // Call onLoginSuccess to close modal and update state
3416
+ if (this.config.onLoginSuccess) {
3417
+ this.config.onLoginSuccess({ user: null });
3418
+ }
3419
+ }
3420
+ else {
3421
+ throw new Error('Passkey signup handler not configured');
3422
+ }
3423
+ }
3424
+ catch (error) {
3425
+ this.showPasskeyError(error instanceof Error ? error.message : 'Failed to signup with passkey');
3426
+ if (this.config.onLoginError) {
3427
+ this.config.onLoginError(error instanceof Error ? error : new Error('Failed to signup with passkey'));
3428
+ }
3429
+ }
3430
+ finally {
3431
+ this.setLoading(false);
3432
+ }
3433
+ }
3434
+ /**
3435
+ * Show passkey-specific error near the passkey controls
3436
+ */
3437
+ showPasskeyError(message) {
3438
+ if (!this.passkeyErrorElement)
3439
+ return;
3440
+ this.passkeyErrorElement.textContent = message;
3441
+ this.passkeyErrorElement.style.display = 'block';
3442
+ }
3443
+ /**
3444
+ * Hide passkey-specific error
3445
+ */
3446
+ hidePasskeyError() {
3447
+ if (!this.passkeyErrorElement)
3448
+ return;
3449
+ this.passkeyErrorElement.textContent = '';
3450
+ this.passkeyErrorElement.style.display = 'none';
3451
+ }
3452
+ }
3453
+ export default OnboardingUIWeb;
3454
+ //# sourceMappingURL=OnboardingUIWeb.js.map