@ibgib/web-gib 0.0.59 → 0.0.60

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 (53) hide show
  1. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  2. package/dist/AUTO-GENERATED-version.mjs +1 -1
  3. package/dist/identity/custodian-delegate-helper.d.mts.map +1 -1
  4. package/dist/identity/custodian-delegate-helper.mjs +20 -3
  5. package/dist/identity/custodian-delegate-helper.mjs.map +1 -1
  6. package/dist/identity/identity-constants.mjs +2 -2
  7. package/dist/identity/identity-constants.mjs.map +1 -1
  8. package/dist/identity/identity-types.d.mts +4 -4
  9. package/dist/identity/identity-types.d.mts.map +1 -1
  10. package/dist/ui/component/identity/add-device/add-device.css +93 -17
  11. package/dist/ui/component/identity/add-device/add-device.d.mts +24 -7
  12. package/dist/ui/component/identity/add-device/add-device.d.mts.map +1 -1
  13. package/dist/ui/component/identity/add-device/add-device.html +97 -23
  14. package/dist/ui/component/identity/add-device/add-device.mjs +490 -186
  15. package/dist/ui/component/identity/add-device/add-device.mjs.map +1 -1
  16. package/dist/ui/component/identity/keystone-creator/keystone-creator.d.mts.map +1 -1
  17. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs +7 -4
  18. package/dist/ui/component/identity/keystone-creator/keystone-creator.mjs.map +1 -1
  19. package/dist/ui/component/identity/keystone-details/keystone-details.css +1 -1
  20. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts +8 -8
  21. package/dist/ui/component/identity/keystone-details/keystone-details.d.mts.map +1 -1
  22. package/dist/ui/component/identity/keystone-details/keystone-details.html +29 -20
  23. package/dist/ui/component/identity/keystone-details/keystone-details.mjs +51 -51
  24. package/dist/ui/component/identity/keystone-details/keystone-details.mjs.map +1 -1
  25. package/dist/ui/component/identity/remove-device/remove-device.d.mts +1 -1
  26. package/dist/ui/component/identity/remove-device/remove-device.d.mts.map +1 -1
  27. package/dist/ui/component/identity/remove-device/remove-device.html +3 -3
  28. package/dist/ui/component/identity/remove-device/remove-device.mjs +7 -7
  29. package/dist/ui/component/identity/remove-device/remove-device.mjs.map +1 -1
  30. package/dist/util/device-helper.d.mts +30 -0
  31. package/dist/util/device-helper.d.mts.map +1 -0
  32. package/dist/util/device-helper.mjs +90 -0
  33. package/dist/util/device-helper.mjs.map +1 -0
  34. package/dist/util/device-helper.respec.d.mts +2 -0
  35. package/dist/util/device-helper.respec.d.mts.map +1 -0
  36. package/dist/util/device-helper.respec.mjs +83 -0
  37. package/dist/util/device-helper.respec.mjs.map +1 -0
  38. package/package.json +3 -3
  39. package/src/AUTO-GENERATED-version.mts +1 -1
  40. package/src/identity/custodian-delegate-helper.mts +28 -3
  41. package/src/identity/identity-constants.mts +2 -2
  42. package/src/identity/identity-types.mts +4 -4
  43. package/src/ui/component/identity/add-device/add-device.css +93 -17
  44. package/src/ui/component/identity/add-device/add-device.html +97 -23
  45. package/src/ui/component/identity/add-device/add-device.mts +577 -194
  46. package/src/ui/component/identity/keystone-creator/keystone-creator.mts +7 -4
  47. package/src/ui/component/identity/keystone-details/keystone-details.css +1 -1
  48. package/src/ui/component/identity/keystone-details/keystone-details.html +29 -20
  49. package/src/ui/component/identity/keystone-details/keystone-details.mts +57 -57
  50. package/src/ui/component/identity/remove-device/remove-device.html +3 -3
  51. package/src/ui/component/identity/remove-device/remove-device.mts +8 -8
  52. package/src/util/device-helper.mts +104 -0
  53. package/src/util/device-helper.respec.mts +90 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @module util/device-helper
3
+ *
4
+ * Pure utility functions for generating clean, friendly, auto-generated device names/tags.
5
+ */
6
+ /**
7
+ * Generates a friendly, URL-safe, kebab-cased default device tag.
8
+ * (e.g., `chrome-win-a1b2`, `mobile-safari-ios-3c4d`, `tablet-android-e5f6`).
9
+ *
10
+ * Pure function: Uses global browser objects (`navigator`, `window`) only as fallbacks
11
+ * when explicit options are omitted, enabling deterministic unit testing.
12
+ *
13
+ * @param opts Optional mock options for deterministic unit testing.
14
+ */
15
+ export function generateDefaultDeviceTag(opts) {
16
+ const ua = (opts?.userAgent ?? (typeof navigator !== 'undefined' ? navigator.userAgent : '') ?? '').toLowerCase();
17
+ const uaData = opts?.userAgentData ?? (typeof navigator !== 'undefined' ? navigator.userAgentData : undefined);
18
+ const touchPoints = opts?.maxTouchPoints ?? (typeof navigator !== 'undefined' ? navigator.maxTouchPoints : 0) ?? 0;
19
+ const width = opts?.screenWidth ?? (typeof window !== 'undefined' ? window.innerWidth : 1024) ?? 1024;
20
+ // 1. Detect Form Factor (Mobile, Tablet, Desktop)
21
+ let formFactor = '';
22
+ const isMobileUaData = uaData?.mobile;
23
+ const isMobileUa = /mobile|iphone|ipod|android.*mobile|windows phone/i.test(ua);
24
+ const isTabletUa = /ipad|tablet|(android(?!.*mobile))/i.test(ua) || (touchPoints > 1 && /macintosh/i.test(ua) && width <= 1366);
25
+ if (isTabletUa) {
26
+ formFactor = 'tablet';
27
+ }
28
+ else if (isMobileUaData || isMobileUa) {
29
+ formFactor = 'mobile';
30
+ }
31
+ // 2. Detect OS / Platform
32
+ let platform = '';
33
+ const uaPlatform = (uaData?.platform || '').toLowerCase();
34
+ if (uaPlatform.includes('win') || ua.includes('windows')) {
35
+ platform = 'win';
36
+ }
37
+ else if (uaPlatform.includes('ios') || ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod')) {
38
+ platform = 'ios';
39
+ }
40
+ else if (uaPlatform.includes('mac') || ua.includes('macintosh') || ua.includes('mac os')) {
41
+ platform = 'mac';
42
+ }
43
+ else if (uaPlatform.includes('android') || ua.includes('android')) {
44
+ platform = 'android';
45
+ }
46
+ else if (uaPlatform.includes('linux') || ua.includes('linux')) {
47
+ platform = 'linux';
48
+ }
49
+ // 3. Detect Browser
50
+ let browser = '';
51
+ if (ua.includes('edg/') || ua.includes('edge/')) {
52
+ browser = 'edge';
53
+ }
54
+ else if (ua.includes('chrome/') || ua.includes('crios/')) {
55
+ browser = 'chrome';
56
+ }
57
+ else if (ua.includes('firefox/') || ua.includes('fxios/')) {
58
+ browser = 'firefox';
59
+ }
60
+ else if (ua.includes('safari/') && !ua.includes('chrome/') && !ua.includes('crios/')) {
61
+ browser = 'safari';
62
+ }
63
+ // 4. Generate or use provided 4-character random suffix
64
+ let suffix = opts?.randomSuffix;
65
+ if (!suffix) {
66
+ const chars = '0123456789abcdef';
67
+ suffix = '';
68
+ for (let i = 0; i < 4; i++) {
69
+ suffix += chars.charAt(Math.floor(Math.random() * chars.length));
70
+ }
71
+ }
72
+ suffix = suffix.toLowerCase().replace(/[^a-z0-9]/g, '').slice(0, 8);
73
+ // 5. Assemble Tokens
74
+ const tokens = [];
75
+ if (formFactor) {
76
+ tokens.push(formFactor);
77
+ }
78
+ if (browser) {
79
+ tokens.push(browser);
80
+ }
81
+ if (platform) {
82
+ tokens.push(platform);
83
+ }
84
+ if (tokens.length === 0) {
85
+ tokens.push('device');
86
+ }
87
+ tokens.push(suffix);
88
+ return tokens.join('-');
89
+ }
90
+ //# sourceMappingURL=device-helper.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-helper.mjs","sourceRoot":"","sources":["../../src/util/device-helper.mts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=device-helper.respec.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-helper.respec.d.mts","sourceRoot":"","sources":["../../src/util/device-helper.respec.mts"],"names":[],"mappings":""}
@@ -0,0 +1,83 @@
1
+ import { respecfully, iReckon, ifWe // DO NOT REMOVE ifWeMight or ifWe even if no longer used. We constantly change these when focusing tests
2
+ } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
3
+ const maam = `[${import.meta.url}]`, sir = maam;
4
+ import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
5
+ import { generateDefaultDeviceTag } from './device-helper.mjs';
6
+ await respecfully(sir, 'device-helper', async () => {
7
+ await ifWe(sir, 'generate clean tag for Desktop Windows Chrome', async () => {
8
+ const lc = `[generate clean tag for Desktop Windows Chrome]`;
9
+ try {
10
+ const tag = generateDefaultDeviceTag({
11
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
12
+ userAgentData: { platform: 'Windows', mobile: false },
13
+ randomSuffix: 'a1b2'
14
+ });
15
+ iReckon(sir, tag).asTo('tag value').isGonnaBe('chrome-win-a1b2');
16
+ }
17
+ catch (error) {
18
+ console.error(`${lc} ${extractErrorMsg(error)}`);
19
+ throw error;
20
+ }
21
+ });
22
+ await ifWe(sir, 'generate clean tag for Mobile iOS Safari (iPhone)', async () => {
23
+ const lc = `[generate clean tag for Mobile iOS Safari (iPhone)]`;
24
+ try {
25
+ const tag = generateDefaultDeviceTag({
26
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
27
+ userAgentData: { platform: 'iOS', mobile: true },
28
+ randomSuffix: '3c4d'
29
+ });
30
+ iReckon(sir, tag).asTo('tag value').isGonnaBe('mobile-safari-ios-3c4d');
31
+ }
32
+ catch (error) {
33
+ console.error(`${lc} ${extractErrorMsg(error)}`);
34
+ throw error;
35
+ }
36
+ });
37
+ await ifWe(sir, 'generate clean tag for Android Chrome Phone', async () => {
38
+ const lc = `[generate clean tag for Android Chrome Phone]`;
39
+ try {
40
+ const tag = generateDefaultDeviceTag({
41
+ userAgent: 'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',
42
+ userAgentData: { platform: 'Android', mobile: true },
43
+ randomSuffix: '9e8f'
44
+ });
45
+ iReckon(sir, tag).asTo('tag value').isGonnaBe('mobile-chrome-android-9e8f');
46
+ }
47
+ catch (error) {
48
+ console.error(`${lc} ${extractErrorMsg(error)}`);
49
+ throw error;
50
+ }
51
+ });
52
+ await ifWe(sir, 'generate clean tag for iPad / Tablet', async () => {
53
+ const lc = `[generate clean tag for iPad / Tablet]`;
54
+ try {
55
+ const tag = generateDefaultDeviceTag({
56
+ userAgent: 'Mozilla/5.0 (iPad; CPU OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
57
+ maxTouchPoints: 5,
58
+ screenWidth: 1024,
59
+ randomSuffix: '7a8b'
60
+ });
61
+ iReckon(sir, tag).asTo('tag value').isGonnaBe('tablet-safari-ios-7a8b');
62
+ }
63
+ catch (error) {
64
+ console.error(`${lc} ${extractErrorMsg(error)}`);
65
+ throw error;
66
+ }
67
+ });
68
+ await ifWe(sir, 'generate fallback tag with random 4-char suffix when no hints are present', async () => {
69
+ const lc = `[generate fallback tag with random 4-char suffix]`;
70
+ try {
71
+ const tag = generateDefaultDeviceTag({
72
+ userAgent: '',
73
+ randomSuffix: '1234'
74
+ });
75
+ iReckon(sir, tag).asTo('tag value').isGonnaBe('device-1234');
76
+ }
77
+ catch (error) {
78
+ console.error(`${lc} ${extractErrorMsg(error)}`);
79
+ throw error;
80
+ }
81
+ });
82
+ });
83
+ //# sourceMappingURL=device-helper.respec.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-helper.respec.mjs","sourceRoot":"","sources":["../../src/util/device-helper.respec.mts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibgib/web-gib",
3
- "version": "0.0.59",
3
+ "version": "0.0.60",
4
4
  "description": "Framework for creating agentic ibGib web apps. Contains plumbing for ibgib components, agentic framework (currently only Gemini implemented), web-based IndexedDB storage substrate, and more.",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -29,10 +29,10 @@
29
29
  "license": "ISC",
30
30
  "dependencies": {
31
31
  "@google/genai": "^1.33.0",
32
- "@ibgib/core-gib": "^0.1.73",
32
+ "@ibgib/core-gib": "^0.1.74",
33
33
  "@ibgib/encrypt-gib": "^0.3.1",
34
34
  "@ibgib/helper-gib": "^0.0.37",
35
- "@ibgib/ts-gib": "^0.5.33"
35
+ "@ibgib/ts-gib": "^0.5.34"
36
36
  },
37
37
  "engines": {
38
38
  "node": ">=19.0.0"
@@ -11,4 +11,4 @@
11
11
  /**
12
12
  * this is the version of this package, auto-updated in the build process
13
13
  */
14
- export const AUTO_GENERATED_VERSION = '0.0.59';
14
+ export const AUTO_GENERATED_VERSION = '0.0.60';
@@ -6,6 +6,8 @@
6
6
  */
7
7
 
8
8
  import { getUUID, hash, HashAlgorithm, extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
9
+ import { storageGet, storagePut } from "../storage/storage-helpers.web.mjs";
10
+ import { getGlobalDbName, getGlobalStoreName } from "../helpers.web.mjs";
9
11
 
10
12
  const LOCAL_STORAGE_PREFIX = 'ibgib_custodian_delegate_secret_';
11
13
 
@@ -27,20 +29,43 @@ export async function deriveLocalCustodianDelegateSecret({
27
29
  }
28
30
 
29
31
  const key = `${LOCAL_STORAGE_PREFIX}${primaryTjpAddr}`;
30
- const existingSecret = localStorage.getItem(key);
32
+
33
+ const dbName = getGlobalDbName();
34
+ const storeName = getGlobalStoreName();
35
+
36
+ // debugger; // what are the dbName and storeName?
37
+
38
+
39
+ const existingSecret = await storageGet({ dbName, storeName, key });
31
40
  if (existingSecret) {
32
41
  return existingSecret;
33
42
  }
34
43
 
35
44
  // Generate high-entropy 512-bit SHA hash secret from fresh UUIDs
36
- const rawEntropy = `${await getUUID()}-${await getUUID()}-${await getUUID()}-${Date.now()}`;
45
+ const rawEntropy = await getUUID(32);
37
46
  const newSecret = await hash({
38
47
  s: rawEntropy,
39
48
  algorithm: HashAlgorithm.sha_512
40
49
  });
50
+ await storagePut({ dbName, storeName, key, value: newSecret });
41
51
 
42
- localStorage.setItem(key, newSecret);
43
52
  return newSecret;
53
+
54
+
55
+ // const existingSecret = localStorage.getItem(key);
56
+ // if (existingSecret) {
57
+ // return existingSecret;
58
+ // }
59
+
60
+ // // Generate high-entropy 512-bit SHA hash secret from fresh UUIDs
61
+ // const rawEntropy = `${await getUUID()}-${await getUUID()}-${await getUUID()}-${Date.now()}`;
62
+ // const newSecret = await hash({
63
+ // s: rawEntropy,
64
+ // algorithm: HashAlgorithm.sha_512
65
+ // });
66
+
67
+ // localStorage.setItem(key, newSecret);
68
+ // return newSecret;
44
69
  } catch (error) {
45
70
  console.error(`${lc} ${extractErrorMsg(error)}`);
46
71
  throw error;
@@ -65,8 +65,8 @@ export const IDENTITY_POOL_ID_APP_EPOCH = 'identity-app-epoch';
65
65
 
66
66
  // ===========================================================================
67
67
  // KDF SALTS
68
- // Used to derive tier-specific MasterSecrets from a root passphrase.
69
- // Different salt per tier ensures independent secrets even with same passphrase.
68
+ // Used to derive tier-specific MasterSecrets from a root password.
69
+ // Different salt per tier ensures independent secrets even with same password.
70
70
  // ===========================================================================
71
71
 
72
72
  export const IDENTITY_KDF_SALT_META = 'ibgib-identity-meta-v1';
@@ -221,17 +221,17 @@ export interface IIdentityService {
221
221
  // ===========================================================================
222
222
 
223
223
  /**
224
- * Inputs for deriving a tier-specific MasterSecret from a root passphrase.
224
+ * Inputs for deriving a tier-specific MasterSecret from a root password.
225
225
  *
226
226
  * Each tier uses a different salt to ensure independent MasterSecrets
227
- * even when the same passphrase is used for all tiers.
227
+ * even when the same password is used for all tiers.
228
228
  *
229
229
  * @see docs/identity/KEYSTONES.md for per-tier salt conventions
230
230
  * @see identity-helpers.mts for derivation functions
231
231
  */
232
232
  export interface MasterSecretDerivationContext {
233
- /** The root passphrase (sovereign path) or server secret (custodial path). */
234
- passphrase: string;
233
+ /** The root password (sovereign path) or server secret (custodial path). */
234
+ password: string;
235
235
 
236
236
  /**
237
237
  * Tier-specific salt (e.g., `IDENTITY_KDF_SALT_META`, `IDENTITY_KDF_SALT_LOCAL`).
@@ -5,22 +5,75 @@
5
5
  }
6
6
 
7
7
  .component-container {
8
- padding: 1.5rem;
8
+ padding: 1.75rem;
9
9
  background: var(--ibgib-bg-surface, #1e1e2e);
10
10
  color: var(--ibgib-text-primary, #cdd6f4);
11
11
  border-radius: 12px;
12
12
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
13
13
  }
14
14
 
15
+ .hidden {
16
+ display: none !important;
17
+ }
18
+
19
+ .sso-options {
20
+ display: flex;
21
+ flex-direction: column;
22
+ gap: 0.75rem;
23
+ }
24
+
25
+ .sso-btn {
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ gap: 0.75rem;
30
+ padding: 0.8rem 1.25rem;
31
+ border: 1px solid rgba(255, 255, 255, 0.15);
32
+ border-radius: 8px;
33
+ background: #181825;
34
+ color: #cdd6f4;
35
+ font-size: 0.95rem;
36
+ font-weight: 600;
37
+ cursor: pointer;
38
+ transition: transform 0.15s ease, background 0.15s ease, border-color 0.15s ease;
39
+ }
40
+
41
+ .sso-btn:hover {
42
+ transform: translateY(-1px);
43
+ background: #313244;
44
+ border-color: #89b4fa;
45
+ }
46
+
47
+ .divider {
48
+ display: flex;
49
+ align-items: center;
50
+ text-align: center;
51
+ margin: 0.75rem 0;
52
+ color: #a6adc8;
53
+ font-size: 0.75rem;
54
+ font-weight: 700;
55
+ letter-spacing: 1px;
56
+ }
57
+
58
+ .divider::before, .divider::after {
59
+ content: '';
60
+ flex: 1;
61
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
62
+ }
63
+
64
+ .divider span {
65
+ padding: 0 0.75rem;
66
+ }
67
+
15
68
  .input-group {
16
- margin-bottom: 1.2rem;
69
+ margin-bottom: 1rem;
17
70
  display: flex;
18
71
  flex-direction: column;
19
72
  gap: 0.4rem;
20
73
  }
21
74
 
22
75
  .input-group label {
23
- font-size: 0.95rem;
76
+ font-size: 0.92rem;
24
77
  font-weight: 500;
25
78
  color: var(--ibgib-text-secondary, #a6adc8);
26
79
  }
@@ -50,7 +103,7 @@
50
103
  }
51
104
 
52
105
  .action-row {
53
- margin-top: 1.5rem;
106
+ margin-top: 1rem;
54
107
  }
55
108
 
56
109
  .action-btn {
@@ -58,7 +111,7 @@
58
111
  padding: 0.85rem 1.5rem;
59
112
  border: none;
60
113
  border-radius: 8px;
61
- background: linear-gradient(135deg, #50fa7b, #40c767);
114
+ background: linear-gradient(135deg, #89b4fa, #74c7ec);
62
115
  color: #11111b;
63
116
  font-size: 1rem;
64
117
  font-weight: 600;
@@ -75,6 +128,12 @@
75
128
  transform: translateY(0);
76
129
  }
77
130
 
131
+ .action-btn:disabled {
132
+ opacity: 0.6;
133
+ cursor: not-allowed;
134
+ transform: none;
135
+ }
136
+
78
137
  .status-area {
79
138
  margin-top: 1.2rem;
80
139
  padding: 1rem;
@@ -100,34 +159,51 @@
100
159
  color: #a6e3a1;
101
160
  }
102
161
 
103
- .hidden {
104
- display: none !important;
105
- }
106
-
107
- .collapsed {
108
- display: none !important;
162
+ .status-msg {
163
+ margin: 0;
164
+ line-height: 1.4;
165
+ word-break: break-word;
109
166
  }
110
167
 
111
168
  .result-area {
112
169
  margin-top: 1.5rem;
113
- padding: 1.2rem;
114
- border-radius: 8px;
115
- background: rgba(166, 227, 161, 0.1);
170
+ padding: 1.25rem;
171
+ background: rgba(166, 227, 161, 0.08);
116
172
  border: 1px solid #a6e3a1;
173
+ border-radius: 8px;
174
+ }
175
+
176
+ .result-area.collapsed {
177
+ display: none;
117
178
  }
118
179
 
119
180
  .result-area h3 {
120
181
  margin-top: 0;
182
+ margin-bottom: 0.5rem;
121
183
  color: #a6e3a1;
184
+ font-size: 1.15rem;
185
+ }
186
+
187
+ .address-label {
188
+ margin-bottom: 0.25rem;
189
+ font-size: 0.85rem;
190
+ color: var(--ibgib-text-secondary, #a6adc8);
122
191
  }
123
192
 
124
193
  .keystone-addr {
125
194
  display: block;
126
- word-break: break-all;
127
- padding: 0.6rem;
195
+ padding: 0.5rem 0.75rem;
128
196
  background: #11111b;
129
197
  border-radius: 6px;
130
198
  font-family: monospace;
131
199
  font-size: 0.85rem;
132
- color: #89b4fa;
200
+ color: #f9e2af;
201
+ word-break: break-all;
202
+ margin-bottom: 0.75rem;
203
+ }
204
+
205
+ .warning {
206
+ margin: 0;
207
+ font-size: 0.85rem;
208
+ color: #a6adc8;
133
209
  }
@@ -1,37 +1,111 @@
1
1
  <div id="container" class="component-container">
2
- <h2 id="title">Sign In New Device</h2>
2
+ <h2 id="title">Sign In to Device</h2>
3
3
  <div class="content">
4
- <p>Sign in on this device to add it to an existing account.</p>
4
+ <p id="subtitle" style="margin-bottom: 1.25rem; font-size: 0.95rem; color: #a6adc8;">
5
+ Sign in on this device to link and synchronize your identity.
6
+ </p>
5
7
 
6
- <div class="input-group">
7
- <label for="input-identifier">Email</label>
8
- <input type="text" id="input-identifier" placeholder="Enter registered email..." class="text-input">
9
- </div>
8
+ <!-- ----------------------------------------------------------------- -->
9
+ <!-- Surface 1: Main Sign In Entry Surface -->
10
+ <!-- ----------------------------------------------------------------- -->
11
+ <div id="surface-entry">
12
+ <div class="sso-options"
13
+ style="display: flex; flex-direction: column; gap: 0.6rem; margin-bottom: 1.25rem;">
14
+ <button id="btn-google-sso" class="sso-btn google-btn">
15
+ <svg width="18" height="18" viewBox="0 0 24 24">
16
+ <path fill="#4285F4"
17
+ 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" />
18
+ <path fill="#34A853"
19
+ 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" />
20
+ <path fill="#FBBC05"
21
+ 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" />
22
+ <path fill="#EA4335"
23
+ 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" />
24
+ </svg>
25
+ Continue with Google
26
+ </button>
27
+ <button id="btn-github-sso" class="sso-btn github-btn">
28
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
29
+ <path
30
+ 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" />
31
+ </svg>
32
+ Continue with GitHub
33
+ </button>
34
+ </div>
10
35
 
11
- <div class="input-group">
12
- <label for="input-device-tag">Device Name</label>
13
- <input type="text" id="input-device-tag" placeholder="e.g. laptop, phone, living-room..."
14
- class="text-input">
15
- </div>
36
+ <div class="divider">
37
+ <span>OR SIGN IN WITH EMAIL</span>
38
+ </div>
16
39
 
17
- <div class="input-group">
18
- <label for="input-passphrase">Master Passphrase</label>
19
- <input type="password" id="input-passphrase" placeholder="Enter your master passphrase..."
20
- class="secret-input">
40
+ <div class="input-group">
41
+ <label for="input-identifier">Email</label>
42
+ <input type="email" id="input-identifier" placeholder="Enter your registered email..."
43
+ class="text-input">
44
+ </div>
45
+
46
+ <div class="action-row" style="margin-top: 0.75rem;">
47
+ <button id="btn-continue-email" class="action-btn">Continue with Email</button>
48
+ </div>
21
49
  </div>
22
50
 
23
- <div class="checkbox-group" style="margin-bottom: 1.5rem; display: flex; align-items: center; gap: 0.5rem;">
24
- <input type="checkbox" id="checkbox-make-active" checked
25
- style="cursor: pointer; width: 1rem; height: 1rem;">
26
- <label for="checkbox-make-active" style="cursor: pointer; font-size: 0.9rem; user-select: none;">Set as
27
- active device on this browser</label>
51
+ <!-- ----------------------------------------------------------------- -->
52
+ <!-- Surface 2 (Conditional): 6-Digit Email OTP Verification -->
53
+ <!-- ----------------------------------------------------------------- -->
54
+ <div id="section-code-verification" class="input-group hidden"
55
+ style="margin-top: 1.25rem; padding: 1rem; border: 1px dashed #89b4fa; border-radius: 8px; background: rgba(137, 180, 250, 0.05);">
56
+ <label for="input-code" style="font-weight: bold; color: #89b4fa;">✉️ Enter 6-Digit Email Verification
57
+ Code</label>
58
+ <p style="font-size: 0.82rem; color: #a6adc8; margin-top: 0.25rem; margin-bottom: 0.75rem;">Check your email
59
+ for your 6-digit verification code:</p>
60
+ <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
61
+ <input type="text" id="input-code" maxlength="6" placeholder="123456" class="text-input"
62
+ style="font-family: monospace; font-size: 1.2rem; letter-spacing: 4px; text-align: center; max-width: 160px;">
63
+ <button id="btn-verify-code" class="action-btn" style="white-space: nowrap; width: auto;">Verify
64
+ Code</button>
65
+ </div>
28
66
  </div>
29
67
 
30
- <div class="action-row">
31
- <button id="btn-add-device" class="action-btn">Sign In</button>
68
+ <!-- ----------------------------------------------------------------- -->
69
+ <!-- Surface 3 (Conditional): Sovereign Primary Password Entry -->
70
+ <!-- ----------------------------------------------------------------- -->
71
+ <div id="section-sovereign-password" class="input-group hidden"
72
+ style="margin-top: 1.25rem; padding: 1rem; border: 1px dashed #f59e0b; border-radius: 8px; background: rgba(245, 158, 11, 0.05);">
73
+ <label for="input-password" style="font-weight: bold; color: #f59e0b;">🔐 Primary Password
74
+ Required</label>
75
+ <p style="font-size: 0.82rem; color: #a6adc8; margin-top: 0.25rem; margin-bottom: 0.75rem;">This sovereign
76
+ identity requires your master password to authorize this device:</p>
77
+ <div class="input-group">
78
+ <input type="password" id="input-password" placeholder="Enter your master password..."
79
+ class="secret-input text-input">
80
+ </div>
81
+ <div class="action-row" style="margin-top: 0.5rem;">
82
+ <button id="btn-authorize-password" class="action-btn">Authorize & Pair Device</button>
83
+ </div>
32
84
  </div>
33
85
 
34
- <div class="switch-workflow" style="margin-top: 1.2rem; text-align: center; font-size: 0.9rem; color: #a6adc8;">
86
+ <!-- ----------------------------------------------------------------- -->
87
+ <!-- Advanced Settings (Collapsible Device Tag & Active Toggle) -->
88
+ <!-- ----------------------------------------------------------------- -->
89
+ <details id="details-device-options"
90
+ style="margin-top: 1.25rem; font-size: 0.85rem; color: #a6adc8; cursor: pointer;">
91
+ <summary style="user-select: none; font-weight: 500;">⚙️ Advanced Device Options</summary>
92
+ <div style="padding-top: 0.75rem;">
93
+ <div class="input-group" style="margin-bottom: 0.75rem;">
94
+ <label for="input-device-tag" style="font-size: 0.82rem;">Device Name / Tag</label>
95
+ <input type="text" id="input-device-tag" placeholder="e.g. laptop, phone, living-room..."
96
+ class="text-input" style="font-size: 0.88rem; padding: 0.55rem 0.8rem;">
97
+ </div>
98
+ <div class="checkbox-group" style="display: flex; align-items: center; gap: 0.5rem;">
99
+ <input type="checkbox" id="checkbox-make-active" checked
100
+ style="cursor: pointer; width: 0.95rem; height: 0.95rem;">
101
+ <label for="checkbox-make-active"
102
+ style="cursor: pointer; font-size: 0.82rem; user-select: none;">Set as active identity on this
103
+ browser</label>
104
+ </div>
105
+ </div>
106
+ </details>
107
+
108
+ <div class="switch-workflow" style="margin-top: 1.5rem; text-align: center; font-size: 0.9rem; color: #a6adc8;">
35
109
  Need a new identity? <button id="btn-switch-create-account" class="link-btn"
36
110
  style="background: none; border: none; color: #89b4fa; font-weight: 600; cursor: pointer; text-decoration: underline; padding: 0; font-size: 0.9rem;">Create
37
111
  Account</button>