@abstraxn/signer-react 3.3.10 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/src/ConnectButton.css.d.ts +2 -0
  3. package/dist/src/ConnectButton.css.js +1 -0
  4. package/dist/src/ConnectButton.d.ts +0 -1
  5. package/dist/src/ConnectButton.js +7 -6
  6. package/dist/src/ExternalWalletButtons.css.d.ts +2 -0
  7. package/dist/src/ExternalWalletButtons.css.js +1 -0
  8. package/dist/src/ExternalWalletButtons.d.ts +4 -1
  9. package/dist/src/ExternalWalletButtons.js +6 -1
  10. package/dist/src/WalletModal.css +2 -0
  11. package/dist/src/WalletModal.css.d.ts +2 -0
  12. package/dist/src/WalletModal.css.js +1 -0
  13. package/dist/src/WalletModal.d.ts +2 -2
  14. package/dist/src/WalletModal.js +113 -59
  15. package/dist/src/components/AbstraxnLogo.d.ts +17 -0
  16. package/dist/src/components/AbstraxnLogo.js +29 -0
  17. package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.js +406 -338
  18. package/dist/src/components/AbstraxnProvider/useAbstraxnProviderBase.d.ts +7 -0
  19. package/dist/src/components/AbstraxnProvider/useAbstraxnProviderBase.js +9 -0
  20. package/dist/src/components/AbstraxnProvider/useWalletInitialization.js +41 -6
  21. package/dist/src/components/OnboardingUI/OnboardingUI.css +99 -4
  22. package/dist/src/components/OnboardingUI/OnboardingUI.css.d.ts +2 -0
  23. package/dist/src/components/OnboardingUI/OnboardingUI.css.js +1 -0
  24. package/dist/src/components/OnboardingUI/OnboardingUIReact.d.ts +0 -1
  25. package/dist/src/components/OnboardingUI/OnboardingUIReact.js +46 -19
  26. package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +24 -1
  27. package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +348 -160
  28. package/dist/src/components/OnboardingUI/components/Divider.d.ts +7 -0
  29. package/dist/src/components/OnboardingUI/components/Divider.js +9 -0
  30. package/dist/src/components/OnboardingUI/components/EmailForm.d.ts +0 -1
  31. package/dist/src/components/OnboardingUI/components/EmailForm.js +1 -2
  32. package/dist/src/components/OnboardingUI/components/MfaForm.d.ts +0 -1
  33. package/dist/src/components/OnboardingUI/components/MfaForm.js +2 -3
  34. package/dist/src/components/OnboardingUI/components/Modal.d.ts +0 -1
  35. package/dist/src/components/OnboardingUI/components/Modal.js +12 -4
  36. package/dist/src/components/OnboardingUI/components/OtpForm.d.ts +0 -1
  37. package/dist/src/components/OnboardingUI/components/OtpForm.js +2 -3
  38. package/dist/src/components/OnboardingUI/components/PasskeyButton.d.ts +0 -1
  39. package/dist/src/components/OnboardingUI/components/PasskeyButton.js +2 -2
  40. package/dist/src/components/OnboardingUI/components/SocialButtons.d.ts +0 -1
  41. package/dist/src/components/OnboardingUI/components/SocialButtons.js +3 -3
  42. package/dist/src/components/OnboardingUI/onboardingShadowContainer.d.ts +9 -0
  43. package/dist/src/components/OnboardingUI/onboardingShadowContainer.js +36 -0
  44. package/dist/src/components/ShadowHost/ShadowHost.d.ts +18 -0
  45. package/dist/src/components/ShadowHost/ShadowHost.js +123 -0
  46. package/dist/src/components/ShadowHost/ShadowHostContext.d.ts +10 -0
  47. package/dist/src/components/ShadowHost/ShadowHostContext.js +14 -0
  48. package/dist/src/components/ShadowHost/index.d.ts +2 -0
  49. package/dist/src/components/ShadowHost/index.js +3 -0
  50. package/dist/src/components/WalletModal/components/ChainSelector.css +9 -0
  51. package/dist/src/components/WalletModal/components/ChainSelector.css.d.ts +2 -0
  52. package/dist/src/components/WalletModal/components/ChainSelector.css.js +1 -0
  53. package/dist/src/components/WalletModal/components/ChainSelector.d.ts +0 -1
  54. package/dist/src/components/WalletModal/components/ChainSelector.js +78 -23
  55. package/dist/src/components/WalletModal/components/CodeInput.d.ts +12 -0
  56. package/dist/src/components/WalletModal/components/CodeInput.js +39 -0
  57. package/dist/src/components/WalletModal/components/ExportKeyModal.css.d.ts +2 -0
  58. package/dist/src/components/WalletModal/components/ExportKeyModal.css.js +1 -0
  59. package/dist/src/components/WalletModal/components/ExportKeyModal.d.ts +0 -2
  60. package/dist/src/components/WalletModal/components/ExportKeyModal.js +2 -4
  61. package/dist/src/components/WalletModal/components/ExportWarningModal.css.d.ts +2 -0
  62. package/dist/src/components/WalletModal/components/ExportWarningModal.css.js +1 -0
  63. package/dist/src/components/WalletModal/components/ExportWarningModal.d.ts +0 -2
  64. package/dist/src/components/WalletModal/components/ExportWarningModal.js +2 -4
  65. package/dist/src/components/WalletModal/components/ManageWalletModal.css +27 -1
  66. package/dist/src/components/WalletModal/components/ManageWalletModal.css.d.ts +2 -0
  67. package/dist/src/components/WalletModal/components/ManageWalletModal.css.js +1 -0
  68. package/dist/src/components/WalletModal/components/ManageWalletModal.d.ts +0 -1
  69. package/dist/src/components/WalletModal/components/ManageWalletModal.js +66 -148
  70. package/dist/src/components/WalletModal/components/MfaVerifyModal.d.ts +15 -0
  71. package/dist/src/components/WalletModal/components/MfaVerifyModal.js +98 -0
  72. package/dist/src/components/WalletModal/components/PreviewTransactionModal.css +2 -12
  73. package/dist/src/components/WalletModal/components/PreviewTransactionModal.css.d.ts +2 -0
  74. package/dist/src/components/WalletModal/components/PreviewTransactionModal.css.js +1 -0
  75. package/dist/src/components/WalletModal/components/PreviewTransactionModal.d.ts +0 -1
  76. package/dist/src/components/WalletModal/components/PreviewTransactionModal.js +0 -1
  77. package/dist/src/components/WalletModal/components/ReceiveModal.css.d.ts +2 -0
  78. package/dist/src/components/WalletModal/components/ReceiveModal.css.js +1 -0
  79. package/dist/src/components/WalletModal/components/ReceiveModal.d.ts +0 -1
  80. package/dist/src/components/WalletModal/components/ReceiveModal.js +2 -2
  81. package/dist/src/components/WalletModal/components/SendModal.css.d.ts +2 -0
  82. package/dist/src/components/WalletModal/components/SendModal.css.js +1 -0
  83. package/dist/src/components/WalletModal/components/SendModal.d.ts +5 -2
  84. package/dist/src/components/WalletModal/components/SendModal.js +25 -82
  85. package/dist/src/components/WalletModal/components/SignMfaModalHost.d.ts +1 -0
  86. package/dist/src/components/WalletModal/components/SignMfaModalHost.js +50 -0
  87. package/dist/src/components/WalletModal/components/SuccessModal.css.d.ts +2 -0
  88. package/dist/src/components/WalletModal/components/SuccessModal.css.js +1 -0
  89. package/dist/src/components/WalletModal/components/SuccessModal.d.ts +0 -1
  90. package/dist/src/components/WalletModal/components/SuccessModal.js +0 -1
  91. package/dist/src/components/WalletModal/components/TokenSelectorModal.css.d.ts +2 -0
  92. package/dist/src/components/WalletModal/components/TokenSelectorModal.css.js +1 -0
  93. package/dist/src/components/WalletModal/components/TokenSelectorModal.d.ts +0 -1
  94. package/dist/src/components/WalletModal/components/TokenSelectorModal.js +0 -1
  95. package/dist/src/components/WalletModal/components/UserAvatar.js +2 -1
  96. package/dist/src/components/WalletModal/components/index.d.ts +3 -0
  97. package/dist/src/components/WalletModal/components/index.js +2 -0
  98. package/dist/src/components/WalletModal/hooks/useSendTransaction.js +18 -6
  99. package/dist/src/components/icons.d.ts +2 -0
  100. package/dist/src/components/icons.js +4 -0
  101. package/dist/src/hooks.d.ts +58 -3
  102. package/dist/src/hooks.js +172 -30
  103. package/dist/src/index.d.ts +1 -1
  104. package/dist/src/index.js +1 -1
  105. package/dist/src/signMfaBridge.d.ts +7 -0
  106. package/dist/src/signMfaBridge.js +20 -0
  107. package/dist/src/types.d.ts +4 -0
  108. package/dist/src/utils/shadowFocus.d.ts +8 -0
  109. package/dist/src/utils/shadowFocus.js +15 -0
  110. package/dist/src/utils/shadowRegistry.d.ts +23 -0
  111. package/dist/src/utils/shadowRegistry.js +36 -0
  112. package/package.json +9 -4
@@ -1,4 +1,8 @@
1
1
  import { isValidEmail } from "@abstraxn/signer-core";
2
+ import { abstraxnLogoSvgHtml } from "../AbstraxnLogo";
3
+ import { createOnboardingShadowContainer } from "./onboardingShadowContainer";
4
+ import { queryAllAcrossShadowRoots } from "../../utils/shadowRegistry";
5
+ import { getDeepActiveElement } from "../../utils/shadowFocus";
2
6
  // CSS styles embedded as string
3
7
  const ONBOARDING_UI_STYLES = `
4
8
  .onboarding-container {
@@ -19,6 +23,12 @@ const ONBOARDING_UI_STYLES = `
19
23
  .onboarding-container.onboarding-inline {
20
24
  min-height: auto;
21
25
  padding: 0;
26
+ justify-content: flex-start;
27
+ }
28
+
29
+ .onboarding-container.onboarding-inline .onboarding-card {
30
+ max-height: none;
31
+ overflow-y: visible;
22
32
  }
23
33
 
24
34
  /* Modal Styles */
@@ -108,6 +118,99 @@ const ONBOARDING_UI_STYLES = `
108
118
  padding: 0;
109
119
  }
110
120
 
121
+ /* Close (X) button — absolutely positioned in the modal's top-right corner.
122
+ Anchored against .onboarding-modal-content, which is already position: relative. */
123
+ .onboarding-modal-close {
124
+ position: absolute;
125
+ top: 16px;
126
+ right: 16px;
127
+ z-index: 10;
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ width: 32px;
132
+ height: 32px;
133
+ padding: 0;
134
+ border: none;
135
+ border-radius: 50%;
136
+ background: transparent;
137
+ color: #6b7280;
138
+ cursor: pointer;
139
+ transition: background-color 0.15s ease, color 0.15s ease;
140
+ }
141
+
142
+ .onboarding-modal-close:hover {
143
+ background-color: rgba(0, 0, 0, 0.06);
144
+ color: #111827;
145
+ }
146
+
147
+ .onboarding-modal-close.onboarding-theme-dark {
148
+ color: #9ca3af;
149
+ }
150
+
151
+ .onboarding-modal-close.onboarding-theme-dark:hover {
152
+ background-color: rgba(255, 255, 255, 0.08);
153
+ color: #f9fafb;
154
+ }
155
+
156
+ /* Back button — used on the OTP and MFA verification screens */
157
+ .onboarding-otp-back-button {
158
+ align-self: flex-start;
159
+ width: 40px;
160
+ height: 40px;
161
+ min-width: 40px;
162
+ min-height: 40px;
163
+ display: flex !important;
164
+ align-items: center;
165
+ justify-content: center;
166
+ background-color: transparent !important;
167
+ border: none;
168
+ border-radius: 8px;
169
+ cursor: pointer;
170
+ transition: all 0.2s ease;
171
+ color: #6b7280 !important;
172
+ padding: 0;
173
+ margin: 0;
174
+ box-shadow: none !important;
175
+ }
176
+
177
+ .onboarding-otp-back-button svg {
178
+ width: 20px;
179
+ height: 20px;
180
+ flex-shrink: 0;
181
+ }
182
+
183
+ .onboarding-otp-back-button:hover:not(:disabled) {
184
+ background-color: #f3f4f6 !important;
185
+ color: #111827 !important;
186
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05) !important;
187
+ }
188
+
189
+ .onboarding-otp-back-button:active:not(:disabled) {
190
+ background-color: #e5e7eb !important;
191
+ transform: scale(0.95);
192
+ }
193
+
194
+ .onboarding-otp-back-button:disabled {
195
+ opacity: 0.3;
196
+ cursor: not-allowed;
197
+ }
198
+
199
+ .onboarding-theme-dark .onboarding-otp-back-button {
200
+ background-color: transparent !important;
201
+ color: #9ca3af !important;
202
+ box-shadow: none !important;
203
+ }
204
+
205
+ .onboarding-theme-dark .onboarding-otp-back-button:hover:not(:disabled) {
206
+ background-color: rgba(255, 255, 255, 0.1) !important;
207
+ color: #ffffff !important;
208
+ }
209
+
210
+ .onboarding-theme-dark .onboarding-otp-back-button:active:not(:disabled) {
211
+ background-color: rgba(255, 255, 255, 0.15) !important;
212
+ }
213
+
111
214
  /* Light Theme */
112
215
  .onboarding-theme-light {
113
216
  color: #000000;
@@ -770,7 +873,9 @@ const ONBOARDING_UI_STYLES = `
770
873
  .onboarding-otp-verification .onboarding-button {
771
874
  /* Match OTP inputs width: 6 inputs × 44px + 5 gaps × 10px = 314px */
772
875
  width: 314px;
773
- max-width: 314px;
876
+ max-width: 100%;
877
+ margin-left: auto;
878
+ margin-right: auto;
774
879
  }
775
880
 
776
881
  .onboarding-otp-icon-container {
@@ -975,6 +1080,51 @@ const ONBOARDING_UI_STYLES = `
975
1080
  cursor: not-allowed;
976
1081
  }
977
1082
 
1083
+ .onboarding-mfa-trouble-text {
1084
+ font-size: 14px;
1085
+ margin: -6px 0 16px;
1086
+ color: #6b7280;
1087
+ text-align: center;
1088
+ }
1089
+
1090
+ .onboarding-theme-dark .onboarding-mfa-trouble-text {
1091
+ color: #9ca3af;
1092
+ }
1093
+
1094
+ .onboarding-mfa-backup-link {
1095
+ border: none !important;
1096
+ background: transparent !important;
1097
+ padding: 0 !important;
1098
+ margin: 0;
1099
+ text-decoration: underline;
1100
+ cursor: pointer;
1101
+ font-weight: 600;
1102
+ font-size: 14px;
1103
+ font-family: inherit;
1104
+ line-height: inherit;
1105
+ box-shadow: none !important;
1106
+ outline: none;
1107
+ appearance: none;
1108
+ -webkit-appearance: none;
1109
+ }
1110
+
1111
+ .onboarding-theme-light .onboarding-mfa-backup-link {
1112
+ color: #111827;
1113
+ }
1114
+
1115
+ .onboarding-theme-dark .onboarding-mfa-backup-link {
1116
+ color: #e5e7eb;
1117
+ }
1118
+
1119
+ .onboarding-mfa-backup-link:hover:not(:disabled) {
1120
+ opacity: 0.8;
1121
+ }
1122
+
1123
+ .onboarding-mfa-backup-link:disabled {
1124
+ opacity: 0.6;
1125
+ cursor: not-allowed;
1126
+ }
1127
+
978
1128
  .onboarding-otp-resend {
979
1129
  text-align: center;
980
1130
  margin-top: 20px;
@@ -1081,7 +1231,15 @@ const ONBOARDING_UI_STYLES = `
1081
1231
  * Works with vanilla JavaScript - no framework dependencies required.
1082
1232
  */
1083
1233
  export class OnboardingUIWeb {
1084
- static stylesInjected = false;
1234
+ // Instance-level, not class-static: each instance now owns its own shadow
1235
+ // root (see shadowContainer below), so a shared class-static flag would
1236
+ // wrongly make a second instance skip injecting styles into its own new
1237
+ // shadow root, leaving it completely unstyled.
1238
+ stylesInjected = false;
1239
+ shadowContainer = null;
1240
+ shadowTeardown = null;
1241
+ /** Pending OAuth loader timer from checkUrlParams — cleared in destroy() so StrictMode cleanup cannot paint into a torn-down shadow root. */
1242
+ pendingLoadingModalTimer = null;
1085
1243
  config;
1086
1244
  container = null;
1087
1245
  rootElement = null;
@@ -1150,10 +1308,48 @@ export class OnboardingUIWeb {
1150
1308
  this.externalWalletDivider.style.display = "";
1151
1309
  }
1152
1310
  }
1311
+ /** True when this instance still owns an attached shadow container. */
1312
+ hasLiveShadow() {
1313
+ return !!this.shadowContainer?.isConnected;
1314
+ }
1315
+ /**
1316
+ * Rebuild the shadow tree if destroy()/StrictMode tore it down.
1317
+ * Safe to call before showLoadingModal / showOnboarding recovery paths.
1318
+ */
1319
+ ensureLiveShadow() {
1320
+ if (this.hasLiveShadow())
1321
+ return;
1322
+ this.init();
1323
+ }
1153
1324
  /**
1154
1325
  * Initialize and render the component
1155
1326
  */
1156
1327
  init() {
1328
+ // Re-init after destroy / StrictMode: drop any stale host first so we never
1329
+ // accumulate orphaned shadow roots on document.body.
1330
+ if (this.shadowTeardown) {
1331
+ try {
1332
+ this.shadowTeardown();
1333
+ }
1334
+ catch {
1335
+ // Ignore teardown errors on a partially-detached host.
1336
+ }
1337
+ this.shadowTeardown = null;
1338
+ this.shadowContainer = null;
1339
+ this.stylesInjected = false;
1340
+ this.modalOverlay = null;
1341
+ }
1342
+ const shadow = createOnboardingShadowContainer();
1343
+ this.shadowContainer = shadow.container;
1344
+ this.shadowTeardown = shadow.teardown;
1345
+ // Same inline stacking-context-defense styles as the React ShadowHost —
1346
+ // prevents ordinary host-page global CSS from promoting this host into
1347
+ // its own stacking context and trapping the modal's z-index inside it.
1348
+ shadow.hostElement.style.position = "static";
1349
+ shadow.hostElement.style.zIndex = "auto";
1350
+ shadow.hostElement.style.transform = "none";
1351
+ shadow.hostElement.style.opacity = "1";
1352
+ shadow.hostElement.style.isolation = "auto";
1157
1353
  this.injectStyles();
1158
1354
  this.setupContainer();
1159
1355
  this.render();
@@ -1187,8 +1383,16 @@ export class OnboardingUIWeb {
1187
1383
  // Don't show "Verifying login..." - provider will show MFA screen instead; avoid it appearing after MFA modal
1188
1384
  return;
1189
1385
  }
1386
+ if (this.pendingLoadingModalTimer !== null) {
1387
+ clearTimeout(this.pendingLoadingModalTimer);
1388
+ this.pendingLoadingModalTimer = null;
1389
+ }
1190
1390
  requestAnimationFrame(() => {
1191
- setTimeout(() => {
1391
+ this.pendingLoadingModalTimer = setTimeout(() => {
1392
+ this.pendingLoadingModalTimer = null;
1393
+ // Skip if destroy() already ran (StrictMode cleanup) — remount re-inits and shows the loader.
1394
+ if (!this.hasLiveShadow())
1395
+ return;
1192
1396
  this.showLoadingModal();
1193
1397
  }, 100);
1194
1398
  });
@@ -1301,6 +1505,7 @@ export class OnboardingUIWeb {
1301
1505
  // OK Button
1302
1506
  const okButton = this.createElement("button", {
1303
1507
  className: "onboarding-button onboarding-button-primary",
1508
+ part: "button-primary",
1304
1509
  textContent: "OK",
1305
1510
  style: "width: 100%; max-width: 200px;",
1306
1511
  });
@@ -1354,15 +1559,24 @@ export class OnboardingUIWeb {
1354
1559
  * Public method to allow external access
1355
1560
  */
1356
1561
  showLoadingModal() {
1357
- // Hide any other modals that might be showing (wallet selection, etc.)
1358
- const otherModals = document.querySelectorAll(".onboarding-modal-overlay, .external-wallet-modal, .wallet-modal-overlay");
1562
+ // Shadow DOM: the loader must live inside this instance's shadow container.
1563
+ // After StrictMode destroy() (or any tear-down), that container is gone —
1564
+ // rebuild before painting, otherwise the overlay is created detached and
1565
+ // never appears (pre-shadow appends went to document.body and survived).
1566
+ this.ensureLiveShadow();
1567
+ if (!this.shadowContainer)
1568
+ return;
1569
+ // Hide any other modals that might be showing (wallet selection, etc.) —
1570
+ // now possibly living in a different shadow root, so this must query
1571
+ // across every registered boundary, not just the real document.
1572
+ const otherModals = queryAllAcrossShadowRoots(".onboarding-modal-overlay, .external-wallet-modal, .wallet-modal-overlay");
1359
1573
  otherModals.forEach((modal) => {
1360
1574
  if (modal.id !== "onboarding-loading-modal-overlay") {
1361
1575
  modal.style.display = "none";
1362
1576
  }
1363
1577
  });
1364
1578
  // Create modal overlay if it doesn't exist
1365
- let loadingModalOverlay = document.getElementById("onboarding-loading-modal-overlay");
1579
+ let loadingModalOverlay = this.shadowContainer?.querySelector("#onboarding-loading-modal-overlay");
1366
1580
  if (!loadingModalOverlay) {
1367
1581
  loadingModalOverlay = this.createElement("div", {
1368
1582
  id: "onboarding-loading-modal-overlay",
@@ -1454,7 +1668,7 @@ export class OnboardingUIWeb {
1454
1668
  `,
1455
1669
  });
1456
1670
  // Add keyframes if not exists
1457
- if (!document.getElementById("onboarding-spin-style")) {
1671
+ if (!this.shadowContainer?.querySelector("#onboarding-spin-style")) {
1458
1672
  const style = document.createElement("style");
1459
1673
  style.id = "onboarding-spin-style";
1460
1674
  style.textContent = `
@@ -1462,7 +1676,7 @@ export class OnboardingUIWeb {
1462
1676
  to { transform: rotate(360deg); }
1463
1677
  }
1464
1678
  `;
1465
- document.head.appendChild(style);
1679
+ this.shadowContainer?.appendChild(style);
1466
1680
  }
1467
1681
  // Text
1468
1682
  const text = this.createElement("p", {
@@ -1478,10 +1692,7 @@ export class OnboardingUIWeb {
1478
1692
  loadingCard.appendChild(text);
1479
1693
  // Footer "Powered by Abstraxn"
1480
1694
  if (this.config.showFooter !== false) {
1481
- const textColor = this.config.colors?.text ||
1482
- (this.config.theme === "dark" ? "#9ca3af" : "#6b7280");
1483
- const brandColor = this.config.colors?.text ||
1484
- (this.config.theme === "dark" ? "#e5e7eb" : "#374151");
1695
+ const { textColor, brandColor } = this.getFooterTextColors();
1485
1696
  const footer = this.createElement("div", {
1486
1697
  className: "onboarding-footer",
1487
1698
  });
@@ -1502,24 +1713,7 @@ export class OnboardingUIWeb {
1502
1713
  "aria-label": "Visit Abstraxn website",
1503
1714
  });
1504
1715
  const logoSvg = this.createElement("span", {
1505
- innerHTML: `
1506
- <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;">
1507
- <path d="M47.9082 37.191L23.9541 23.553L0 9.91504L37.7436 -8.13502e-06L47.9082 37.191Z" fill="url(#paint0_linear_1242_781)"/>
1508
- <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)"/>
1509
- <defs>
1510
- <linearGradient id="paint0_linear_1242_781" x1="7.03362" y1="6.09543" x2="47.2003" y2="57.0656" gradientUnits="userSpaceOnUse">
1511
- <stop stop-color="#F878D2"/>
1512
- <stop offset="0.393522" stop-color="#FDCC81"/>
1513
- <stop offset="1" stop-color="#F9A0D9"/>
1514
- </linearGradient>
1515
- <linearGradient id="paint1_linear_1242_781" x1="2.53739" y1="21.5136" x2="48.7969" y2="38.5742" gradientUnits="userSpaceOnUse">
1516
- <stop stop-color="#F878D2"/>
1517
- <stop offset="0.393522" stop-color="#FDCC81"/>
1518
- <stop offset="1" stop-color="#F9A0D9"/>
1519
- </linearGradient>
1520
- </defs>
1521
- </svg>
1522
- `,
1716
+ innerHTML: abstraxnLogoSvgHtml({ size: 20, gradientId: "abstraxn_logo_web_error" }),
1523
1717
  style: "display: inline-flex; align-items: center;",
1524
1718
  });
1525
1719
  brandContainer.appendChild(logoSvg);
@@ -1533,7 +1727,7 @@ export class OnboardingUIWeb {
1533
1727
  loadingCard.appendChild(footer);
1534
1728
  }
1535
1729
  loadingModalOverlay.appendChild(loadingCard);
1536
- document.body.appendChild(loadingModalOverlay);
1730
+ this.shadowContainer?.appendChild(loadingModalOverlay);
1537
1731
  }
1538
1732
  else {
1539
1733
  // Ensure modal is visible
@@ -1556,7 +1750,7 @@ export class OnboardingUIWeb {
1556
1750
  * Hide loading modal overlay
1557
1751
  */
1558
1752
  hideLoadingModal() {
1559
- const loadingModalOverlay = document.getElementById("onboarding-loading-modal-overlay");
1753
+ const loadingModalOverlay = this.shadowContainer?.querySelector("#onboarding-loading-modal-overlay");
1560
1754
  if (loadingModalOverlay) {
1561
1755
  loadingModalOverlay.style.display = "none";
1562
1756
  loadingModalOverlay.style.visibility = "hidden";
@@ -1571,7 +1765,7 @@ export class OnboardingUIWeb {
1571
1765
  const displayMessage = this.normalizeErrorMessage(message);
1572
1766
  // Helper function to close error modal
1573
1767
  const closeErrorModal = () => {
1574
- const overlay = document.getElementById("onboarding-error-modal-overlay");
1768
+ const overlay = this.shadowContainer?.querySelector("#onboarding-error-modal-overlay");
1575
1769
  if (overlay) {
1576
1770
  overlay.style.display = "none";
1577
1771
  document.body.style.overflow = "";
@@ -1582,7 +1776,7 @@ export class OnboardingUIWeb {
1582
1776
  }
1583
1777
  };
1584
1778
  // Create modal overlay if it doesn't exist
1585
- let errorModalOverlay = document.getElementById("onboarding-error-modal-overlay");
1779
+ let errorModalOverlay = this.shadowContainer?.querySelector("#onboarding-error-modal-overlay");
1586
1780
  if (!errorModalOverlay) {
1587
1781
  errorModalOverlay = this.createElement("div", {
1588
1782
  id: "onboarding-error-modal-overlay",
@@ -1701,6 +1895,7 @@ export class OnboardingUIWeb {
1701
1895
  // OK Button
1702
1896
  const okButton = this.createElement("button", {
1703
1897
  className: "onboarding-button onboarding-button-primary",
1898
+ part: "button-primary",
1704
1899
  textContent: "OK",
1705
1900
  style: "width: 100%; max-width: 200px;",
1706
1901
  });
@@ -1708,10 +1903,7 @@ export class OnboardingUIWeb {
1708
1903
  errorCard.appendChild(okButton);
1709
1904
  // Footer "Powered by Abstraxn"
1710
1905
  if (this.config.showFooter !== false) {
1711
- const textColor = this.config.colors?.text ||
1712
- (this.config.theme === "dark" ? "#9ca3af" : "#6b7280");
1713
- const brandColor = this.config.colors?.text ||
1714
- (this.config.theme === "dark" ? "#e5e7eb" : "#374151");
1906
+ const { textColor, brandColor } = this.getFooterTextColors();
1715
1907
  const footer = this.createElement("div", {
1716
1908
  className: "onboarding-footer",
1717
1909
  });
@@ -1732,24 +1924,7 @@ export class OnboardingUIWeb {
1732
1924
  "aria-label": "Visit Abstraxn website",
1733
1925
  });
1734
1926
  const logoSvg = this.createElement("span", {
1735
- innerHTML: `
1736
- <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;">
1737
- <path d="M47.9082 37.191L23.9541 23.553L0 9.91504L37.7436 -8.13502e-06L47.9082 37.191Z" fill="url(#paint0_linear_error_modal)"/>
1738
- <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)"/>
1739
- <defs>
1740
- <linearGradient id="paint0_linear_error_modal" x1="7.03362" y1="6.09543" x2="47.2003" y2="57.0656" gradientUnits="userSpaceOnUse">
1741
- <stop stop-color="#F878D2"/>
1742
- <stop offset="0.393522" stop-color="#FDCC81"/>
1743
- <stop offset="1" stop-color="#F9A0D9"/>
1744
- </linearGradient>
1745
- <linearGradient id="paint1_linear_error_modal" x1="2.53739" y1="21.5136" x2="48.7969" y2="38.5742" gradientUnits="userSpaceOnUse">
1746
- <stop stop-color="#F878D2"/>
1747
- <stop offset="0.393522" stop-color="#FDCC81"/>
1748
- <stop offset="1" stop-color="#F9A0D9"/>
1749
- </linearGradient>
1750
- </defs>
1751
- </svg>
1752
- `,
1927
+ innerHTML: abstraxnLogoSvgHtml({ size: 20, gradientId: "abstraxn_logo_web_error_modal" }),
1753
1928
  style: "display: inline-flex; align-items: center;",
1754
1929
  });
1755
1930
  brandContainer.appendChild(logoSvg);
@@ -1763,7 +1938,7 @@ export class OnboardingUIWeb {
1763
1938
  errorCard.appendChild(footer);
1764
1939
  }
1765
1940
  errorModalOverlay.appendChild(errorCard);
1766
- document.body.appendChild(errorModalOverlay);
1941
+ this.shadowContainer?.appendChild(errorModalOverlay);
1767
1942
  }
1768
1943
  else {
1769
1944
  // Update message if overlay exists
@@ -1779,32 +1954,26 @@ export class OnboardingUIWeb {
1779
1954
  * Inject CSS styles into the document head (only once)
1780
1955
  */
1781
1956
  injectStyles() {
1782
- if (OnboardingUIWeb.stylesInjected) {
1957
+ if (this.stylesInjected || !this.shadowContainer) {
1783
1958
  return;
1784
1959
  }
1785
- // Check if styles are already injected
1786
- const existingStyle = document.getElementById("onboarding-ui-styles");
1960
+ // Defensive DOM-existence check (stylesInjected already guards this
1961
+ // per-instance, but this is cheap and matches the idempotency pattern
1962
+ // used elsewhere).
1963
+ const existingStyle = this.shadowContainer.querySelector("#onboarding-ui-styles");
1787
1964
  if (existingStyle) {
1788
- OnboardingUIWeb.stylesInjected = true;
1965
+ this.stylesInjected = true;
1789
1966
  return;
1790
1967
  }
1791
1968
  // Create and inject style element
1792
1969
  const styleElement = document.createElement("style");
1793
1970
  styleElement.id = "onboarding-ui-styles";
1794
1971
  styleElement.textContent = ONBOARDING_UI_STYLES;
1795
- document.head.appendChild(styleElement);
1796
- // Inject custom CSS if provided
1797
- if (this.config.customCSS) {
1798
- const customStyleElement = document.createElement("style");
1799
- customStyleElement.id = "onboarding-ui-custom-styles";
1800
- customStyleElement.textContent = this.config.customCSS;
1801
- document.head.appendChild(customStyleElement);
1802
- }
1972
+ this.shadowContainer.appendChild(styleElement);
1803
1973
  // Apply custom colors if provided
1804
1974
  if (this.config.colors) {
1805
1975
  this.applyCustomColors();
1806
1976
  }
1807
- OnboardingUIWeb.stylesInjected = true;
1808
1977
  // Inject utility styles for loading/error modes
1809
1978
  const utilityStyle = document.createElement("style");
1810
1979
  utilityStyle.id = "onboarding-utility-styles";
@@ -1814,15 +1983,61 @@ export class OnboardingUIWeb {
1814
1983
  display: none !important;
1815
1984
  }
1816
1985
  `;
1817
- document.head.appendChild(utilityStyle);
1986
+ this.shadowContainer.appendChild(utilityStyle);
1987
+ // Custom CSS is injected strictly last, always — deliberate reorder from
1988
+ // the pre-migration order (base styles -> customCSS -> colors -> utility)
1989
+ // so customCSS always wins over both base styles and colors on
1990
+ // equal-specificity rules. See docs/shadow-dom-migration-plan.md Section 9.
1991
+ if (this.config.customCSS) {
1992
+ const customStyleElement = document.createElement("style");
1993
+ customStyleElement.id = "onboarding-ui-custom-styles";
1994
+ customStyleElement.textContent = this.config.customCSS;
1995
+ this.shadowContainer.appendChild(customStyleElement);
1996
+ }
1997
+ this.stylesInjected = true;
1998
+ }
1999
+ /**
2000
+ * Build a divider element (line + "or" text) matching `.onboarding-divider`
2001
+ */
2002
+ createDividerElement() {
2003
+ const divider = this.createElement("div", {
2004
+ className: "onboarding-divider",
2005
+ });
2006
+ const dividerText = this.createElement("span", {
2007
+ className: "onboarding-divider-text",
2008
+ textContent: "or",
2009
+ });
2010
+ divider.appendChild(dividerText);
2011
+ return divider;
2012
+ }
2013
+ /**
2014
+ * Footer text/brand colors, derived from config.colors.text with theme-based fallbacks
2015
+ */
2016
+ getFooterTextColors() {
2017
+ const textColor = this.config.colors?.text ||
2018
+ (this.config.theme === "dark" ? "#9ca3af" : "#6b7280");
2019
+ const brandColor = this.config.colors?.text ||
2020
+ (this.config.theme === "dark" ? "#e5e7eb" : "#374151");
2021
+ return { textColor, brandColor };
2022
+ }
2023
+ /**
2024
+ * Passkey button colors, derived from config.colors with theme-based fallbacks
2025
+ */
2026
+ getPasskeyButtonColors() {
2027
+ const passkeyBg = this.config.colors?.primary ||
2028
+ (this.config.theme === "dark" ? "#4b5563" : "#ffffff");
2029
+ const passkeyBorder = this.config.colors?.border ||
2030
+ (this.config.theme === "dark" ? "#6b7280" : "#e5e7eb");
2031
+ const passkeyTextColor = this.config.colors?.text ||
2032
+ (this.config.theme === "dark" ? "#ffffff" : "#1f2937");
2033
+ return { passkeyBg, passkeyBorder, passkeyTextColor };
1818
2034
  }
1819
2035
  /**
1820
2036
  * Apply custom colors from config
1821
2037
  */
1822
2038
  applyCustomColors() {
1823
- if (!this.config.colors)
2039
+ if (!this.config.colors || !this.shadowContainer)
1824
2040
  return;
1825
- const root = this.rootElement || document.documentElement;
1826
2041
  const style = document.createElement("style");
1827
2042
  style.id = "onboarding-ui-colors";
1828
2043
  let css = "";
@@ -1847,15 +2062,16 @@ export class OnboardingUIWeb {
1847
2062
  css += `.onboarding-error { color: ${this.config.colors.error} !important; }`;
1848
2063
  }
1849
2064
  style.textContent = css;
1850
- document.head.appendChild(style);
2065
+ this.shadowContainer.appendChild(style);
1851
2066
  }
1852
2067
  /**
1853
2068
  * Setup the container element
1854
2069
  */
1855
2070
  setupContainer() {
1856
- // For modal mode, always use body
2071
+ // For modal mode, always use the shadow container (the boundary this
2072
+ // instance owns), not document.body directly.
1857
2073
  if (this.config.modal) {
1858
- this.container = document.body;
2074
+ this.container = this.shadowContainer;
1859
2075
  }
1860
2076
  else {
1861
2077
  if (this.config.container) {
@@ -1870,8 +2086,8 @@ export class OnboardingUIWeb {
1870
2086
  }
1871
2087
  }
1872
2088
  else {
1873
- // Create a default container
1874
- this.container = document.body;
2089
+ // Default container: the shadow container this instance owns.
2090
+ this.container = this.shadowContainer;
1875
2091
  }
1876
2092
  }
1877
2093
  }
@@ -1908,6 +2124,7 @@ export class OnboardingUIWeb {
1908
2124
  // Create root element for the form
1909
2125
  this.rootElement = this.createElement("div", {
1910
2126
  className: `onboarding-container onboarding-theme-${this.config.theme} ${this.config.className}`,
2127
+ part: "container",
1911
2128
  style: this.parseStyle(this.config.style),
1912
2129
  });
1913
2130
  // Build the form content
@@ -1938,10 +2155,11 @@ export class OnboardingUIWeb {
1938
2155
  }
1939
2156
  });
1940
2157
  }
1941
- // Prevent body scroll when modal is open
2158
+ // Prevent body scroll when modal is open (intentional real document.body
2159
+ // side effect, not a style leak — the host page's own scroll state).
1942
2160
  document.body.style.overflow = "hidden";
1943
- // Append to body
1944
- document.body.appendChild(this.modalOverlay);
2161
+ // Append inside this instance's own shadow container, not document.body.
2162
+ this.shadowContainer?.appendChild(this.modalOverlay);
1945
2163
  // Add animation class
1946
2164
  setTimeout(() => {
1947
2165
  if (this.modalOverlay) {
@@ -1969,11 +2187,12 @@ export class OnboardingUIWeb {
1969
2187
  const focusableElements = this.modalContent.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
1970
2188
  const firstElement = focusableElements[0];
1971
2189
  const lastElement = focusableElements[focusableElements.length - 1];
1972
- if (e.shiftKey && document.activeElement === firstElement) {
2190
+ const active = getDeepActiveElement();
2191
+ if (e.shiftKey && active === firstElement) {
1973
2192
  e.preventDefault();
1974
2193
  lastElement.focus();
1975
2194
  }
1976
- else if (!e.shiftKey && document.activeElement === lastElement) {
2195
+ else if (!e.shiftKey && active === lastElement) {
1977
2196
  e.preventDefault();
1978
2197
  firstElement.focus();
1979
2198
  }
@@ -1987,12 +2206,13 @@ export class OnboardingUIWeb {
1987
2206
  // Create root element
1988
2207
  this.rootElement = this.createElement("div", {
1989
2208
  className: `onboarding-container onboarding-inline onboarding-theme-${this.config.theme} ${this.config.className}`,
2209
+ part: "container",
1990
2210
  style: this.parseStyle(this.config.style),
1991
2211
  });
1992
2212
  // Build the form content
1993
2213
  this.buildFormContent();
1994
2214
  // Append to container
1995
- if (this.container === document.body) {
2215
+ if (this.container === this.shadowContainer) {
1996
2216
  this.container.appendChild(this.rootElement);
1997
2217
  }
1998
2218
  else {
@@ -2007,7 +2227,7 @@ export class OnboardingUIWeb {
2007
2227
  if (!this.rootElement)
2008
2228
  return;
2009
2229
  // Create card
2010
- const card = this.createElement("div", { className: "onboarding-card" });
2230
+ const card = this.createElement("div", { className: "onboarding-card", part: "card" });
2011
2231
  // Header with logo at top
2012
2232
  const header = this.createElement("div", {
2013
2233
  className: "onboarding-header",
@@ -2079,6 +2299,7 @@ export class OnboardingUIWeb {
2079
2299
  id: "onboarding-email-input",
2080
2300
  placeholder: this.config.labels?.emailPlaceholder || "Enter your email",
2081
2301
  className: "onboarding-input",
2302
+ part: "input",
2082
2303
  required: true,
2083
2304
  autocomplete: "email",
2084
2305
  "aria-label": this.config.labels?.emailLabel || "Email Address",
@@ -2139,6 +2360,7 @@ export class OnboardingUIWeb {
2139
2360
  id: "onboarding-otp-input",
2140
2361
  placeholder: this.config.labels?.otpPlaceholder || "Enter 6-digit code",
2141
2362
  className: "onboarding-input",
2363
+ part: "input",
2142
2364
  required: false, // Don't require initially since it's hidden
2143
2365
  maxLength: 6,
2144
2366
  inputMode: "numeric",
@@ -2161,6 +2383,7 @@ export class OnboardingUIWeb {
2161
2383
  this.continueButton = this.createElement("button", {
2162
2384
  type: "submit",
2163
2385
  className: "onboarding-button onboarding-button-primary",
2386
+ part: "button-primary",
2164
2387
  textContent: this.config.labels?.emailButton || "Continue",
2165
2388
  "aria-label": this.config.labels?.emailButton || "Continue with email",
2166
2389
  style: allAuthMethodsEnabled ? "display: none;" : "",
@@ -2180,14 +2403,7 @@ export class OnboardingUIWeb {
2180
2403
  }
2181
2404
  // Divider (show if email plus any social method is enabled)
2182
2405
  if (showEmail && socialMethods.length > 0) {
2183
- this.divider = this.createElement("div", {
2184
- className: "onboarding-divider",
2185
- });
2186
- const dividerText = this.createElement("span", {
2187
- className: "onboarding-divider-text",
2188
- textContent: "or",
2189
- });
2190
- this.divider.appendChild(dividerText);
2406
+ this.divider = this.createDividerElement();
2191
2407
  card.appendChild(this.divider);
2192
2408
  }
2193
2409
  else {
@@ -2210,6 +2426,7 @@ export class OnboardingUIWeb {
2210
2426
  className: isGoogle
2211
2427
  ? "onboarding-button onboarding-button-google"
2212
2428
  : "onboarding-button onboarding-button-social",
2429
+ part: isGoogle ? "button-google" : undefined,
2213
2430
  "aria-label": this.config.labels?.[`${method}Button`] ||
2214
2431
  (method === "google"
2215
2432
  ? "Continue with Google"
@@ -2287,14 +2504,7 @@ export class OnboardingUIWeb {
2287
2504
  }
2288
2505
  // Passkey divider (only show if passkey is enabled and there are other auth methods)
2289
2506
  if (showPasskey && (showEmail || socialMethods.length > 0)) {
2290
- this.passkeyDivider = this.createElement("div", {
2291
- className: "onboarding-divider",
2292
- });
2293
- const passkeyDividerText = this.createElement("span", {
2294
- className: "onboarding-divider-text",
2295
- textContent: "or",
2296
- });
2297
- this.passkeyDivider.appendChild(passkeyDividerText);
2507
+ this.passkeyDivider = this.createDividerElement();
2298
2508
  card.appendChild(this.passkeyDivider);
2299
2509
  }
2300
2510
  else {
@@ -2302,12 +2512,7 @@ export class OnboardingUIWeb {
2302
2512
  }
2303
2513
  // Passkey login button (only show if passkey auth is enabled)
2304
2514
  if (showPasskey) {
2305
- const passkeyBg = this.config.colors?.primary ||
2306
- (this.config.theme === "dark" ? "#4b5563" : "#ffffff");
2307
- const passkeyBorder = this.config.colors?.border ||
2308
- (this.config.theme === "dark" ? "#6b7280" : "#e5e7eb");
2309
- const passkeyTextColor = this.config.colors?.text ||
2310
- (this.config.theme === "dark" ? "#ffffff" : "#1f2937");
2515
+ const { passkeyBg, passkeyBorder, passkeyTextColor } = this.getPasskeyButtonColors();
2311
2516
  this.passkeyLoginButton = this.createElement("button", {
2312
2517
  type: "button",
2313
2518
  className: "onboarding-button onboarding-button-passkey",
@@ -2366,15 +2571,10 @@ export class OnboardingUIWeb {
2366
2571
  card.appendChild(this.passkeyErrorElement);
2367
2572
  }
2368
2573
  // External wallet divider (shown immediately if external wallets are enabled)
2369
- this.externalWalletDivider = this.createElement("div", {
2370
- className: "onboarding-divider",
2371
- style: this.externalWalletsEnabled ? "" : "display: none;",
2372
- });
2373
- const externalWalletDividerText = this.createElement("span", {
2374
- className: "onboarding-divider-text",
2375
- textContent: "or",
2376
- });
2377
- this.externalWalletDivider.appendChild(externalWalletDividerText);
2574
+ this.externalWalletDivider = this.createDividerElement();
2575
+ this.externalWalletDivider.style.display = this.externalWalletsEnabled
2576
+ ? ""
2577
+ : "none";
2378
2578
  card.appendChild(this.externalWalletDivider);
2379
2579
  // External wallet container (shown immediately if external wallets are enabled)
2380
2580
  this.externalWalletContainer = this.createElement("div", {
@@ -2384,10 +2584,7 @@ export class OnboardingUIWeb {
2384
2584
  card.appendChild(this.externalWalletContainer);
2385
2585
  // Footer (inside card, so it appears on initial login screen)
2386
2586
  if (this.config.showFooter) {
2387
- const textColor = this.config.colors?.text ||
2388
- (this.config.theme === "dark" ? "#9ca3af" : "#6b7280");
2389
- const brandColor = this.config.colors?.text ||
2390
- (this.config.theme === "dark" ? "#e5e7eb" : "#374151");
2587
+ const { textColor, brandColor } = this.getFooterTextColors();
2391
2588
  this.footer = this.createElement("div", {
2392
2589
  className: "onboarding-footer",
2393
2590
  });
@@ -2411,24 +2608,7 @@ export class OnboardingUIWeb {
2411
2608
  });
2412
2609
  // Abstraxn logo SVG
2413
2610
  const logoSvg = this.createElement("span", {
2414
- innerHTML: `
2415
- <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;">
2416
- <path d="M47.9082 37.191L23.9541 23.553L0 9.91504L37.7436 -8.13502e-06L47.9082 37.191Z" fill="url(#paint0_linear_1242_781)"/>
2417
- <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)"/>
2418
- <defs>
2419
- <linearGradient id="paint0_linear_1242_781" x1="7.03362" y1="6.09543" x2="47.2003" y2="57.0656" gradientUnits="userSpaceOnUse">
2420
- <stop stop-color="#F878D2"/>
2421
- <stop offset="0.393522" stop-color="#FDCC81"/>
2422
- <stop offset="1" stop-color="#F9A0D9"/>
2423
- </linearGradient>
2424
- <linearGradient id="paint1_linear_1242_781" x1="2.53739" y1="21.5136" x2="48.7969" y2="38.5742" gradientUnits="userSpaceOnUse">
2425
- <stop stop-color="#F878D2"/>
2426
- <stop offset="0.393522" stop-color="#FDCC81"/>
2427
- <stop offset="1" stop-color="#F9A0D9"/>
2428
- </linearGradient>
2429
- </defs>
2430
- </svg>
2431
- `,
2611
+ innerHTML: abstraxnLogoSvgHtml({ size: 20, gradientId: "abstraxn_logo_web_main" }),
2432
2612
  style: "display: inline-flex; align-items: center;",
2433
2613
  });
2434
2614
  brandContainer.appendChild(logoSvg);
@@ -2835,6 +3015,7 @@ export class OnboardingUIWeb {
2835
3015
  this.verifyButton = this.createElement("button", {
2836
3016
  type: "button",
2837
3017
  className: "onboarding-button onboarding-button-primary",
3018
+ part: "button-primary",
2838
3019
  textContent: "Verify",
2839
3020
  });
2840
3021
  this.verifyButton.disabled = true; // Start disabled; updateVerifyButtonDisabled() keeps it in sync
@@ -2867,10 +3048,7 @@ export class OnboardingUIWeb {
2867
3048
  }
2868
3049
  // Footer (add to OTP screen as well)
2869
3050
  if (this.config.showFooter) {
2870
- const textColor = this.config.colors?.text ||
2871
- (this.config.theme === "dark" ? "#9ca3af" : "#6b7280");
2872
- const brandColor = this.config.colors?.text ||
2873
- (this.config.theme === "dark" ? "#e5e7eb" : "#374151");
3051
+ const { textColor, brandColor } = this.getFooterTextColors();
2874
3052
  const otpFooter = this.createElement("div", {
2875
3053
  className: "onboarding-footer",
2876
3054
  });
@@ -2894,24 +3072,7 @@ export class OnboardingUIWeb {
2894
3072
  });
2895
3073
  // Abstraxn logo SVG
2896
3074
  const logoSvg = this.createElement("span", {
2897
- innerHTML: `
2898
- <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;">
2899
- <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)"/>
2900
- <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)"/>
2901
- <defs>
2902
- <linearGradient id="paint0_linear_1242_781_otp" x1="7.03362" y1="6.09543" x2="47.2003" y2="57.0656" gradientUnits="userSpaceOnUse">
2903
- <stop stop-color="#F878D2"/>
2904
- <stop offset="0.393522" stop-color="#FDCC81"/>
2905
- <stop offset="1" stop-color="#F9A0D9"/>
2906
- </linearGradient>
2907
- <linearGradient id="paint1_linear_1242_781_otp" x1="2.53739" y1="21.5136" x2="48.7969" y2="38.5742" gradientUnits="userSpaceOnUse">
2908
- <stop stop-color="#F878D2"/>
2909
- <stop offset="0.393522" stop-color="#FDCC81"/>
2910
- <stop offset="1" stop-color="#F9A0D9"/>
2911
- </linearGradient>
2912
- </defs>
2913
- </svg>
2914
- `,
3075
+ innerHTML: abstraxnLogoSvgHtml({ size: 20, gradientId: "abstraxn_logo_web_otp" }),
2915
3076
  style: "display: inline-flex; align-items: center;",
2916
3077
  });
2917
3078
  brandContainer.appendChild(logoSvg);
@@ -3404,6 +3565,7 @@ export class OnboardingUIWeb {
3404
3565
  this.mfaVerifyButton = this.createElement("button", {
3405
3566
  type: "button",
3406
3567
  className: "onboarding-button onboarding-button-primary",
3568
+ part: "button-primary",
3407
3569
  textContent: "Verify",
3408
3570
  });
3409
3571
  this.mfaVerifyButton.disabled = true;
@@ -4056,6 +4218,12 @@ export class OnboardingUIWeb {
4056
4218
  * Destroy the component
4057
4219
  */
4058
4220
  destroy() {
4221
+ // Cancel any delayed OAuth loader so it cannot paint into a torn-down shadow root
4222
+ // (or call ensureLiveShadow and orphan a new host after StrictMode cleanup).
4223
+ if (this.pendingLoadingModalTimer !== null) {
4224
+ clearTimeout(this.pendingLoadingModalTimer);
4225
+ this.pendingLoadingModalTimer = null;
4226
+ }
4059
4227
  // Reset to login form before destroying to ensure clean state
4060
4228
  this.resetToLoginForm();
4061
4229
  // Clean up modal
@@ -4086,6 +4254,26 @@ export class OnboardingUIWeb {
4086
4254
  this.mfaUseBackupCode = false;
4087
4255
  this.externalWalletContainer = null; // Clear external wallet container reference
4088
4256
  this.externalWalletDivider = null; // Clear external wallet divider reference
4257
+ // Remove this instance's own shadow host from document.body and
4258
+ // unregister it from shadowRegistry — entirely self-contained to this
4259
+ // instance, does not affect any other coexisting OnboardingUIWeb instance
4260
+ // (see docs/shadow-dom-migration-plan.md — this was a page-wide singleton
4261
+ // in an earlier draft of the design, corrected to per-instance).
4262
+ this.shadowTeardown?.();
4263
+ this.shadowTeardown = null;
4264
+ this.shadowContainer = null;
4265
+ // MUST reset alongside shadowContainer: a later re-init() creates a
4266
+ // BRAND NEW shadow container with zero styles injected into it yet. If
4267
+ // this stayed true, injectStyles()'s first-line guard
4268
+ // (`if (this.stylesInjected || ...) return;`) would short-circuit before
4269
+ // ever reaching its own DOM-existence check, permanently skipping style
4270
+ // injection for the new container — the modal would render with only
4271
+ // its inline `display` style and none of its own CSS (no position:fixed,
4272
+ // no z-index), landing in normal document flow instead of as an overlay.
4273
+ // This exact sequence happens every time under React 18 StrictMode's
4274
+ // dev-mode mount->cleanup->mount cycle: destroy() runs once via the
4275
+ // simulated cleanup, then a real re-init happens lazily on first click.
4276
+ this.stylesInjected = false;
4089
4277
  }
4090
4278
  /**
4091
4279
  * Create DOM element helper