@ibgib/web-gib 0.0.57 → 0.0.58

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 (41) hide show
  1. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  2. package/dist/AUTO-GENERATED-version.mjs +1 -1
  3. package/dist/api/ibgib-api-bridge.d.mts +63 -1
  4. package/dist/api/ibgib-api-bridge.d.mts.map +1 -1
  5. package/dist/api/ibgib-api-bridge.mjs +135 -0
  6. package/dist/api/ibgib-api-bridge.mjs.map +1 -1
  7. package/dist/identity/custodian-delegate-helper.d.mts +22 -0
  8. package/dist/identity/custodian-delegate-helper.d.mts.map +1 -0
  9. package/dist/identity/custodian-delegate-helper.mjs +50 -0
  10. package/dist/identity/custodian-delegate-helper.mjs.map +1 -0
  11. package/dist/identity/sso-popup-helper.d.mts +33 -0
  12. package/dist/identity/sso-popup-helper.d.mts.map +1 -0
  13. package/dist/identity/sso-popup-helper.mjs +124 -0
  14. package/dist/identity/sso-popup-helper.mjs.map +1 -0
  15. package/dist/index.d.mts +2 -0
  16. package/dist/index.d.mts.map +1 -1
  17. package/dist/index.mjs +3 -0
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/ui/component/identity/identity-header/identity-header.d.mts.map +1 -1
  20. package/dist/ui/component/identity/identity-header/identity-header.mjs +2 -1
  21. package/dist/ui/component/identity/identity-header/identity-header.mjs.map +1 -1
  22. package/dist/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
  23. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts +22 -0
  24. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
  25. package/dist/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
  26. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +386 -63
  27. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
  28. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
  29. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +10 -53
  30. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
  31. package/package.json +4 -4
  32. package/src/AUTO-GENERATED-version.mts +1 -1
  33. package/src/api/ibgib-api-bridge.mts +195 -1
  34. package/src/identity/custodian-delegate-helper.mts +61 -0
  35. package/src/identity/sso-popup-helper.mts +155 -0
  36. package/src/index.mts +3 -0
  37. package/src/ui/component/identity/identity-header/identity-header.mts +2 -1
  38. package/src/ui/component/identity/keystone-creator/keystone-creator.css +109 -23
  39. package/src/ui/component/identity/keystone-creator/keystone-creator.html +122 -60
  40. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +441 -67
  41. package/src/ui/component/identity/keystone-details/keystone-details.mts +10 -62
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @module identity/sso-popup-helper
3
+ *
4
+ * DRY client helper for initiating OAuth2 SSO authentication via a popup window
5
+ * and capturing the returned authorization code.
6
+ */
7
+
8
+ import { extractErrorMsg, getUUID } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
9
+ import { getApiBridge } from "../api/ibgib-api-bridge.mjs";
10
+
11
+ export interface SsoPopupOpts {
12
+ providerId: 'google' | 'github';
13
+ parentTjpAddr?: string;
14
+ }
15
+
16
+ export interface SsoPopupResult {
17
+ success: boolean;
18
+ code?: string;
19
+ stateObj?: any;
20
+ redirectUri?: string;
21
+ message?: string;
22
+ }
23
+
24
+ /**
25
+ * Executes an OAuth2 SSO login/link authorization flow using a popup window.
26
+ *
27
+ * 1. Queries `GET /api/identity/sso/config` for safe public client configs.
28
+ * 2. Constructs the provider authorization URL with encoded state payload.
29
+ * 3. Opens a 600x700 popup window and listens for `sso-oauth-callback` postMessage.
30
+ * 4. Returns `{ success: true, code, stateObj, redirectUri }` upon authorization.
31
+ */
32
+ export async function executeSsoOAuthPopup({
33
+ providerId,
34
+ parentTjpAddr,
35
+ }: SsoPopupOpts): Promise<SsoPopupResult> {
36
+ const lc = `[${executeSsoOAuthPopup.name}]`;
37
+ try {
38
+ // 1. Fetch public SSO configs from server
39
+ const apiBridge = getApiBridge();
40
+ const resConfig = await apiBridge.getSsoConfig();
41
+ if (!resConfig.success || !resConfig.providers) {
42
+ return { success: false, message: resConfig.message || 'SSO configuration retrieval failed.' };
43
+ }
44
+
45
+ const providers = resConfig.providers as any;
46
+ const providerConfig = Array.isArray(providers)
47
+ ? providers.find((p: any) => p.providerId === providerId)
48
+ : providers[providerId];
49
+
50
+ if (!providerConfig) {
51
+ return {
52
+ success: false,
53
+ message: `SSO provider '${providerId}' is not configured on this server environment.`
54
+ };
55
+ }
56
+
57
+ // 2. Generate high-entropy user nonce & state
58
+ /**
59
+ * {@link getUUID} uses {@link globalThis.crypto.getRandomValues}
60
+ */
61
+ const userNonce = await getUUID();
62
+ const stateObj = {
63
+ providerId,
64
+ parentTjpAddr,
65
+ userNonce
66
+ };
67
+ const state = btoa(JSON.stringify(stateObj));
68
+
69
+ // 3. Construct Auth URL if not pre-constructed
70
+ let authUrl = providerConfig.authUrl;
71
+ if (!authUrl.includes('client_id=')) {
72
+ authUrl = `${authUrl}?client_id=${providerConfig.clientId}&redirect_uri=${encodeURIComponent(providerConfig.redirectUri)}&response_type=code&scope=${encodeURIComponent(providerConfig.scope)}&state=${encodeURIComponent(state)}`;
73
+ } else if (!authUrl.includes('state=')) {
74
+ authUrl = `${authUrl}&state=${encodeURIComponent(state)}`;
75
+ }
76
+
77
+ // 4. Open popup window
78
+ const popup = window.open(
79
+ authUrl,
80
+ 'sso-oauth-popup',
81
+ 'width=600,height=700,status=yes,toolbar=no,menubar=no,location=yes'
82
+ );
83
+
84
+ if (!popup) {
85
+ return { success: false, message: 'Popup blocked by browser. Please enable popups for this site.' };
86
+ }
87
+
88
+ // 5. Promise wrapper listening for postMessage or window closure
89
+ return await new Promise<SsoPopupResult>((resolve) => {
90
+ let checkClosedInterval: any;
91
+
92
+ const messageListener = (event: MessageEvent) => {
93
+ if (event.origin !== window.location.origin) return;
94
+ const callbackData = event.data;
95
+ if (callbackData && callbackData.type === 'sso-oauth-callback') {
96
+ window.removeEventListener('message', messageListener);
97
+ clearInterval(checkClosedInterval);
98
+ resolve({
99
+ success: true,
100
+ code: callbackData.code,
101
+ stateObj: callbackData.stateObj || stateObj,
102
+ redirectUri: providerConfig.redirectUri
103
+ });
104
+ }
105
+ };
106
+
107
+ window.addEventListener('message', messageListener);
108
+
109
+ checkClosedInterval = setInterval(() => {
110
+ if (popup.closed) {
111
+ clearInterval(checkClosedInterval);
112
+ window.removeEventListener('message', messageListener);
113
+ resolve({ success: false, message: 'Authentication cancelled (window closed).' });
114
+ }
115
+ }, 500);
116
+ });
117
+
118
+ } catch (error) {
119
+ const emsg = extractErrorMsg(error);
120
+ console.error(`${lc} ${emsg}`);
121
+ return { success: false, message: emsg };
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Checks if the current window was opened as an OAuth popup (`window.opener` exists)
127
+ * and has received an authorization code in the URL query string (`?code=...`).
128
+ * If so, posts `sso-oauth-callback` to the parent window and closes itself.
129
+ */
130
+ export function checkAndNotifyPopupOpener(): boolean {
131
+ if (typeof window === 'undefined' || !window.opener) {
132
+ return false;
133
+ }
134
+
135
+ const url = new URL(window.location.href);
136
+ const code = url.searchParams.get('code');
137
+ if (!code) {
138
+ return false;
139
+ }
140
+
141
+ const rawState = url.searchParams.get('state');
142
+ let stateObj: any;
143
+ if (rawState) {
144
+ try { stateObj = JSON.parse(atob(rawState)); } catch { }
145
+ }
146
+
147
+ window.opener.postMessage({
148
+ type: 'sso-oauth-callback',
149
+ code,
150
+ stateObj
151
+ }, window.location.origin);
152
+
153
+ window.close();
154
+ return true;
155
+ }
package/src/index.mts CHANGED
@@ -18,6 +18,9 @@ export * from "./api/ibgib-api-bridge.mjs";
18
18
 
19
19
  // Identity & Sync services
20
20
  export * from "./identity/keystone-sync-service.mjs";
21
+ export * from "./identity/custodian-delegate-helper.mjs";
22
+ // export * from "./identity/sso-callback-helper.mjs";
23
+ export * from "./identity/sso-popup-helper.mjs";
21
24
 
22
25
  // Identity UI components
23
26
  export * from "./ui/component/identity/index.mjs";
@@ -146,7 +146,8 @@ export class IdentityHeaderComponentInstance
146
146
  };
147
147
  this.keystoneCache.set(addr, details);
148
148
  return details;
149
- } catch {
149
+ } catch (error) {
150
+ console.error(`${this.lc}[getKeystoneDetails] Registered keystone address ${addr} not found in local browser space: ${extractErrorMsg(error)} (E: a123456789abcdef0123456789c0002)`);
150
151
  return { username: '', description: '', isPrimary: false };
151
152
  }
152
153
  }
@@ -10,17 +10,105 @@ h2 {
10
10
  color: var(--clr-accent, #50fa7b);
11
11
  }
12
12
 
13
+ .mode-tabs {
14
+ display: flex;
15
+ gap: 0.5rem;
16
+ background: rgba(0, 0, 0, 0.25);
17
+ padding: 0.25rem;
18
+ border-radius: 10px;
19
+ border: 1px solid var(--clr-border, rgba(255, 255, 255, 0.1));
20
+ margin-bottom: 1rem;
21
+ }
22
+
23
+ .mode-tab {
24
+ flex: 1;
25
+ padding: 0.6rem 0.75rem;
26
+ border: none;
27
+ border-radius: 8px;
28
+ background: transparent;
29
+ color: var(--clr-text-secondary, #a6adc8);
30
+ font-weight: 600;
31
+ font-size: 0.9rem;
32
+ cursor: pointer;
33
+ transition: all 0.2s ease;
34
+ }
35
+
36
+ .mode-tab:hover {
37
+ color: var(--clr-text, #cdd6f4);
38
+ background: rgba(255, 255, 255, 0.05);
39
+ }
40
+
41
+ .mode-tab.active {
42
+ background: #45475a;
43
+ color: #89b4fa;
44
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
45
+ }
46
+
13
47
  .subtitle {
14
48
  color: var(--clr-text-secondary, #a6adc8);
15
- font-size: 0.95rem;
49
+ font-size: 0.92rem;
16
50
  margin: 0;
51
+ line-height: 1.45;
17
52
  }
18
53
 
19
54
  .content {
20
- margin-top: 1.5rem;
55
+ margin-top: 1.25rem;
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: 1.25rem;
59
+ }
60
+
61
+ .tab-panel.hidden, .hidden {
62
+ display: none !important;
63
+ }
64
+
65
+ .sso-buttons {
21
66
  display: flex;
22
67
  flex-direction: column;
23
- gap: 1.5rem;
68
+ gap: 0.75rem;
69
+ }
70
+
71
+ .sso-btn {
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: center;
75
+ gap: 0.75rem;
76
+ padding: 0.8rem 1.25rem;
77
+ border: 1px solid rgba(255, 255, 255, 0.15);
78
+ border-radius: 8px;
79
+ background: #1e1e2e;
80
+ color: #cdd6f4;
81
+ font-size: 0.95rem;
82
+ font-weight: 600;
83
+ cursor: pointer;
84
+ transition: transform 0.15s ease, background 0.15s ease;
85
+ }
86
+
87
+ .sso-btn:hover {
88
+ transform: translateY(-1px);
89
+ background: #313244;
90
+ border-color: #89b4fa;
91
+ }
92
+
93
+ .divider {
94
+ display: flex;
95
+ align-items: center;
96
+ text-align: center;
97
+ margin: 0.5rem 0;
98
+ color: #a6adc8;
99
+ font-size: 0.75rem;
100
+ font-weight: 700;
101
+ letter-spacing: 1px;
102
+ }
103
+
104
+ .divider::before, .divider::after {
105
+ content: '';
106
+ flex: 1;
107
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
108
+ }
109
+
110
+ .divider span {
111
+ padding: 0 0.75rem;
24
112
  }
25
113
 
26
114
  .input-group {
@@ -34,8 +122,25 @@ h2 {
34
122
  color: var(--clr-text-secondary, #a6adc8);
35
123
  }
36
124
 
125
+ .text-input {
126
+ width: 100%;
127
+ padding: 0.75rem 1rem;
128
+ border-radius: 8px;
129
+ border: 1px solid rgba(255, 255, 255, 0.15);
130
+ background: #11111b;
131
+ color: #cdd6f4;
132
+ font-size: 0.95rem;
133
+ box-sizing: border-box;
134
+ }
135
+
136
+ .text-input:focus {
137
+ outline: none;
138
+ border-color: #89b4fa;
139
+ box-shadow: 0 0 0 2px rgba(137, 180, 250, 0.2);
140
+ }
141
+
37
142
  .action-row {
38
- margin-top: 1rem;
143
+ margin-top: 0.5rem;
39
144
  width: 100%;
40
145
  }
41
146
 
@@ -65,10 +170,6 @@ h2 {
65
170
  margin-top: 0.5rem;
66
171
  }
67
172
 
68
- .status-area.hidden {
69
- display: none;
70
- }
71
-
72
173
  .result-area {
73
174
  padding: 1.5rem;
74
175
  background: rgba(255, 255, 255, 0.03);
@@ -77,7 +178,6 @@ h2 {
77
178
  display: flex;
78
179
  flex-direction: column;
79
180
  gap: 0.75rem;
80
- transition: opacity var(--t-med, 0.2s ease);
81
181
  }
82
182
 
83
183
  .result-area.collapsed {
@@ -99,18 +199,4 @@ h2 {
99
199
  input::placeholder {
100
200
  color: rgba(255, 255, 255, 0.35);
101
201
  opacity: 1;
102
- }
103
-
104
- .section-code-verification {
105
- margin-top: 1.5rem;
106
- padding: 1.25rem;
107
- border: 2px dashed #f59e0b;
108
- background: rgba(245, 158, 11, 0.05);
109
- box-shadow: 0 0 15px rgba(245, 158, 11, 0.2);
110
- border-radius: 12px;
111
- transition: all 0.3s ease;
112
- }
113
-
114
- .section-code-verification.hidden {
115
- display: none;
116
202
  }
@@ -1,61 +1,123 @@
1
- <div id="container" class="component-container">
2
- <h2 id="title">Create Account</h2>
3
- <div class="content">
4
- <p class="subtitle">Create your sovereign identity and register it to begin.</p>
5
-
6
- <div class="input-group">
7
- <label for="input-username">Username</label>
8
- <input type="text" id="input-username" placeholder="Enter your username..." class="text-input">
9
- </div>
10
-
11
- <div class="input-group">
12
- <label for="input-description">Description (Optional)</label>
13
- <input type="text" id="input-description" placeholder="Enter an optional description..." class="text-input">
14
- </div>
15
-
16
- <div class="input-group">
17
- <label for="input-email">Primary Email (Optional)</label>
18
- <input type="email" id="input-email" placeholder="Enter your primary email address..." class="text-input">
19
- <span class="privacy-note" style="font-size: 0.78rem; color: #a6adc8; margin-top: 0.25rem;">ℹ️ Primary email is optional and used for account discovery. If provided, it is linked to your public identity graph.</span>
20
- </div>
21
-
22
- <div class="input-group">
23
- <label for="input-secret">Master Secret (Keep this private!)</label>
24
- <input type="password" id="input-secret" placeholder="Enter your master secret..." class="secret-input">
25
- </div>
26
-
27
- <div class="checkbox-group" style="margin-bottom: 1.5rem; display: flex; align-items: center; gap: 0.5rem;">
28
- <input type="checkbox" id="checkbox-make-active" checked style="cursor: pointer; width: 1rem; height: 1rem;">
29
- <label for="checkbox-make-active" style="cursor: pointer; font-size: 0.9rem; user-select: none;">Make this the active identity</label>
30
- </div>
31
-
32
- <div class="action-row">
33
- <button id="btn-generate" class="action-btn">Generate Identity Keystone</button>
34
- </div>
35
-
36
- <div class="switch-workflow" style="margin-top: 1.2rem; text-align: center; font-size: 0.9rem; color: #a6adc8;">
37
- Already have an account? <button id="btn-switch-sign-in" class="link-btn" style="background: none; border: none; color: #89b4fa; font-weight: 600; cursor: pointer; text-decoration: underline; padding: 0; font-size: 0.9rem;">Sign In</button>
38
- </div>
39
-
40
- <div id="status-area" class="status-area hidden">
41
- <p id="status-text" class="status-msg"></p>
42
- </div>
43
-
44
- <div id="section-code-verification" class="input-group hidden" style="margin-top: 1.5rem; padding: 1rem; border: 1px dashed #89b4fa; border-radius: 8px; background: rgba(137, 180, 250, 0.05);">
45
- <label for="input-verify-code" style="font-weight: bold; color: #89b4fa;">✉️ Enter 6-Digit Email Verification Code</label>
46
- <p style="font-size: 0.82rem; color: #a6adc8; margin-top: 0.25rem; margin-bottom: 0.75rem;">A 6-digit verification code was sent to your email. Enter it below to activate your identity:</p>
47
- <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
48
- <input type="text" id="input-verify-code" maxlength="6" placeholder="123456" class="text-input" style="font-family: monospace; font-size: 1.2rem; letter-spacing: 4px; text-align: center; max-width: 160px;">
49
- <button id="btn-submit-code" class="action-btn" style="white-space: nowrap;">Verify Code</button>
50
- <button id="btn-resend-code" class="action-btn" style="white-space: nowrap; background: #45475a; color: #cdd6f4;">Resend Code</button>
51
- </div>
52
- </div>
53
-
54
- <div id="result-area" class="result-area collapsed">
55
- <h3>New Keystone Generated!</h3>
56
- <p class="address-label">Address:</p>
57
- <code id="keystone-addr" class="keystone-addr"></code>
58
- <p class="warning">⚠️ Store this address safely. It is your root identity for this space.</p>
59
- </div>
60
- </div>
1
+ <div id="container" class="component-container">
2
+ <h2 id="title">Create Account</h2>
3
+
4
+ <!-- Mode Selector Tabs -->
5
+ <div class="mode-tabs">
6
+ <button id="tab-quick" class="mode-tab active">⚡ Quick (Passwordless)</button>
7
+ <button id="tab-password" class="mode-tab">🔑 Password-based</button>
8
+ </div>
9
+
10
+ <!-- Mode Subtitles -->
11
+ <p id="subtitle-quick" class="subtitle">⚡ Skip passwords — sign in with Google, GitHub, or an Email code. You can always set up a password later.</p>
12
+ <p id="subtitle-password" class="subtitle hidden">🔑 Full local ownership — your password never leaves your device. You can link Google or GitHub later.</p>
13
+
14
+ <div class="content">
15
+ <!-- ----------------------------------------------------------------- -->
16
+ <!-- Tab 1: Quick (Passwordless) Content -->
17
+ <!-- ----------------------------------------------------------------- -->
18
+ <div id="panel-quick" class="tab-panel">
19
+ <div class="sso-buttons">
20
+ <button id="btn-google-sso" class="sso-btn google-btn">
21
+ <svg width="18" height="18" viewBox="0 0 24 24"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.06H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.94l2.85-2.22.81-.63z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.06l3.66 2.84c.87-2.6 3.3-4.52 6.16-4.52z"/></svg>
22
+ Continue with Google
23
+ </button>
24
+ <button id="btn-github-sso" class="sso-btn github-btn">
25
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
26
+ Continue with GitHub
27
+ </button>
28
+ </div>
29
+
30
+ <div class="divider">
31
+ <span>OR USE EMAIL CODE</span>
32
+ </div>
33
+
34
+ <div class="input-group">
35
+ <div style="display: flex; align-items: center; gap: 0.4rem;">
36
+ <label for="input-quick-email">Use custom email sign-in</label>
37
+ <button id="btn-email-info" class="info-icon-btn" title="Learn about custom email sign-in" style="background: none; border: none; cursor: pointer; padding: 0; font-size: 0.9rem; line-height: 1;">ℹ️</button>
38
+ </div>
39
+ <input type="email" id="input-quick-email" placeholder="Enter your email address..." class="text-input">
40
+ </div>
41
+
42
+ <div class="action-row" style="margin-top: 0.75rem;">
43
+ <button id="btn-quick-send-code" class="action-btn">Send 6-Digit Verification Code</button>
44
+ </div>
45
+
46
+ <div id="section-quick-otp" class="input-group hidden" style="margin-top: 1.25rem; padding: 1rem; border: 1px dashed #89b4fa; border-radius: 8px; background: rgba(137, 180, 250, 0.05);">
47
+ <label for="input-quick-otp" style="font-weight: bold; color: #89b4fa;">✉️ Enter 6-Digit Email Verification Code</label>
48
+ <p style="font-size: 0.82rem; color: #a6adc8; margin-top: 0.25rem; margin-bottom: 0.75rem;">Check your email for your 6-digit verification code:</p>
49
+ <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
50
+ <input type="text" id="input-quick-otp" maxlength="6" placeholder="123456" class="text-input" style="font-family: monospace; font-size: 1.2rem; letter-spacing: 4px; text-align: center; max-width: 160px;">
51
+ <button id="btn-quick-verify-code" class="action-btn" style="white-space: nowrap; width: auto;">Verify Code</button>
52
+ </div>
53
+ </div>
54
+ </div>
55
+
56
+ <!-- ----------------------------------------------------------------- -->
57
+ <!-- Tab 2: Password-based Content -->
58
+ <!-- ----------------------------------------------------------------- -->
59
+ <div id="panel-password" class="tab-panel hidden">
60
+ <div class="input-group">
61
+ <label for="input-username">Username</label>
62
+ <input type="text" id="input-username" placeholder="Enter your username..." class="text-input">
63
+ </div>
64
+
65
+ <div class="input-group">
66
+ <label for="input-description">Description (Optional)</label>
67
+ <input type="text" id="input-description" placeholder="Enter an optional description..." class="text-input">
68
+ </div>
69
+
70
+ <div class="input-group">
71
+ <label for="input-email">Primary Email (Optional)</label>
72
+ <input type="email" id="input-email" placeholder="Enter your primary email address..." class="text-input">
73
+ </div>
74
+
75
+ <div class="input-group">
76
+ <label for="input-secret">Password (Keep this private!)</label>
77
+ <input type="password" id="input-secret" placeholder="Enter your password..." class="secret-input text-input">
78
+ </div>
79
+
80
+ <div class="checkbox-group" style="margin-top: 0.5rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem;">
81
+ <input type="checkbox" id="checkbox-make-active" checked style="cursor: pointer; width: 1rem; height: 1rem;">
82
+ <label for="checkbox-make-active" style="cursor: pointer; font-size: 0.9rem; user-select: none;">Make this the active identity</label>
83
+ </div>
84
+
85
+ <div class="action-row">
86
+ <button id="btn-generate" class="action-btn">Generate Identity Keystone</button>
87
+ </div>
88
+ </div>
89
+
90
+ <!-- ----------------------------------------------------------------- -->
91
+ <!-- Status & Verification Areas -->
92
+ <!-- ----------------------------------------------------------------- -->
93
+ <div id="status-area" class="status-area hidden">
94
+ <p id="status-text" class="status-msg"></p>
95
+ </div>
96
+
97
+ <div id="section-code-verification" class="input-group hidden" style="margin-top: 1.5rem; padding: 1rem; border: 1px dashed #89b4fa; border-radius: 8px; background: rgba(137, 180, 250, 0.05);">
98
+ <label for="input-verify-code" style="font-weight: bold; color: #89b4fa;">✉️ Enter 6-Digit Email Verification Code</label>
99
+ <p style="font-size: 0.82rem; color: #a6adc8; margin-top: 0.25rem; margin-bottom: 0.75rem;">A 6-digit verification code was sent to your email. Enter it below to activate your identity:</p>
100
+ <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
101
+ <input type="text" id="input-verify-code" maxlength="6" placeholder="123456" class="text-input" style="font-family: monospace; font-size: 1.2rem; letter-spacing: 4px; text-align: center; max-width: 160px;">
102
+ <button id="btn-submit-code" class="action-btn" style="white-space: nowrap;">Verify Code</button>
103
+ <button id="btn-resend-code" class="action-btn" style="white-space: nowrap; background: #45475a; color: #cdd6f4;">Resend Code</button>
104
+ </div>
105
+ </div>
106
+
107
+ <div id="result-area" class="result-area collapsed">
108
+ <h3>New Keystone Generated!</h3>
109
+ <p class="address-label">Address:</p>
110
+ <code id="keystone-addr" class="keystone-addr"></code>
111
+ <p class="warning">⚠️ Store this address safely. It is your root identity for this space.</p>
112
+ </div>
113
+
114
+ <div class="switch-workflow" style="margin-top: 1.2rem; text-align: center; font-size: 0.9rem; color: #a6adc8;">
115
+ Already have a device paired? <button id="btn-switch-sign-in" class="link-btn" style="background: none; border: none; color: #89b4fa; font-weight: 600; cursor: pointer; text-decoration: underline; padding: 0; font-size: 0.9rem;">Pair New Device</button>
116
+ </div>
117
+
118
+ <!-- Privacy Footer Note -->
119
+ <div class="privacy-footer" style="margin-top: 1rem; text-align: center; font-size: 0.78rem; color: #a6adc8; line-height: 1.4;">
120
+ ℹ️ Primary email addresses and identity keystones form part of the public graph for account discovery. Business data requires authorized sync delegates. Learn more in our <a id="link-privacy-policy" href="#privacy" style="color: #89b4fa; text-decoration: underline;">Privacy Policy</a>.
121
+ </div>
122
+ </div>
61
123
  </div>