@glideidentity/web-client-sdk 5.0.0 → 5.0.1-beta.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 (89) hide show
  1. package/README.md +108 -8
  2. package/dist/adapters/angular/index.js +0 -1
  3. package/dist/adapters/angular/phone-auth.service.d.ts +0 -18
  4. package/dist/adapters/angular/phone-auth.service.js +0 -26
  5. package/dist/adapters/react/index.js +0 -3
  6. package/dist/adapters/react/useClient.js +0 -1
  7. package/dist/adapters/react/usePhoneAuth.js +1 -16
  8. package/dist/adapters/vanilla/client.js +0 -1
  9. package/dist/adapters/vanilla/index.js +0 -1
  10. package/dist/adapters/vanilla/phone-auth.js +0 -31
  11. package/dist/adapters/vue/index.js +0 -4
  12. package/dist/adapters/vue/useClient.js +0 -5
  13. package/dist/adapters/vue/usePhoneAuth.js +1 -20
  14. package/dist/browser/web-client-sdk.min.js +1 -1
  15. package/dist/browser.js +0 -6
  16. package/dist/core/client.js +0 -12
  17. package/dist/core/logger.js +1 -81
  18. package/dist/core/phone-auth/api-types.js +0 -83
  19. package/dist/core/phone-auth/client.js +27 -366
  20. package/dist/core/phone-auth/error-utils.js +1 -83
  21. package/dist/core/phone-auth/index.js +0 -1
  22. package/dist/core/phone-auth/status-types.d.ts +0 -78
  23. package/dist/core/phone-auth/status-types.js +0 -17
  24. package/dist/core/phone-auth/strategies/desktop.js +8 -126
  25. package/dist/core/phone-auth/strategies/index.d.ts +0 -4
  26. package/dist/core/phone-auth/strategies/index.js +0 -4
  27. package/dist/core/phone-auth/strategies/link.js +10 -88
  28. package/dist/core/phone-auth/strategies/ts43.d.ts +0 -19
  29. package/dist/core/phone-auth/strategies/ts43.js +2 -33
  30. package/dist/core/phone-auth/strategies/types.js +0 -4
  31. package/dist/core/phone-auth/type-guards.js +0 -131
  32. package/dist/core/phone-auth/types.js +0 -32
  33. package/dist/core/phone-auth/ui/mobile-debug-console.js +2 -28
  34. package/dist/core/phone-auth/ui/modal.d.ts +33 -55
  35. package/dist/core/phone-auth/ui/modal.js +889 -422
  36. package/dist/core/phone-auth/validation-utils.js +2 -40
  37. package/dist/core/version.js +1 -2
  38. package/dist/esm/adapters/angular/index.js +0 -1
  39. package/dist/esm/adapters/angular/phone-auth.service.d.ts +0 -18
  40. package/dist/esm/adapters/angular/phone-auth.service.js +0 -26
  41. package/dist/esm/adapters/react/index.js +0 -3
  42. package/dist/esm/adapters/react/useClient.js +0 -1
  43. package/dist/esm/adapters/react/usePhoneAuth.js +1 -16
  44. package/dist/esm/adapters/vanilla/client.js +0 -1
  45. package/dist/esm/adapters/vanilla/index.js +0 -1
  46. package/dist/esm/adapters/vanilla/phone-auth.d.ts +0 -24
  47. package/dist/esm/adapters/vanilla/phone-auth.js +0 -31
  48. package/dist/esm/adapters/vue/index.js +0 -4
  49. package/dist/esm/adapters/vue/useClient.js +0 -5
  50. package/dist/esm/adapters/vue/usePhoneAuth.js +1 -20
  51. package/dist/esm/browser.js +0 -6
  52. package/dist/esm/core/client.d.ts +0 -10
  53. package/dist/esm/core/client.js +0 -12
  54. package/dist/esm/core/logger.d.ts +0 -53
  55. package/dist/esm/core/logger.js +1 -81
  56. package/dist/esm/core/phone-auth/api-types.d.ts +0 -315
  57. package/dist/esm/core/phone-auth/api-types.js +0 -83
  58. package/dist/esm/core/phone-auth/client.d.ts +0 -144
  59. package/dist/esm/core/phone-auth/client.js +27 -366
  60. package/dist/esm/core/phone-auth/error-utils.d.ts +0 -29
  61. package/dist/esm/core/phone-auth/error-utils.js +1 -83
  62. package/dist/esm/core/phone-auth/index.js +1 -3
  63. package/dist/esm/core/phone-auth/status-types.d.ts +0 -78
  64. package/dist/esm/core/phone-auth/status-types.js +0 -17
  65. package/dist/esm/core/phone-auth/strategies/desktop.d.ts +0 -63
  66. package/dist/esm/core/phone-auth/strategies/desktop.js +8 -126
  67. package/dist/esm/core/phone-auth/strategies/index.d.ts +0 -4
  68. package/dist/esm/core/phone-auth/strategies/index.js +0 -4
  69. package/dist/esm/core/phone-auth/strategies/link.d.ts +0 -48
  70. package/dist/esm/core/phone-auth/strategies/link.js +10 -88
  71. package/dist/esm/core/phone-auth/strategies/ts43.d.ts +0 -19
  72. package/dist/esm/core/phone-auth/strategies/ts43.js +2 -33
  73. package/dist/esm/core/phone-auth/strategies/types.d.ts +0 -13
  74. package/dist/esm/core/phone-auth/strategies/types.js +0 -4
  75. package/dist/esm/core/phone-auth/type-guards.d.ts +0 -128
  76. package/dist/esm/core/phone-auth/type-guards.js +0 -131
  77. package/dist/esm/core/phone-auth/types.d.ts +0 -108
  78. package/dist/esm/core/phone-auth/types.js +0 -32
  79. package/dist/esm/core/phone-auth/ui/mobile-debug-console.d.ts +0 -4
  80. package/dist/esm/core/phone-auth/ui/mobile-debug-console.js +2 -28
  81. package/dist/esm/core/phone-auth/ui/modal.d.ts +27 -68
  82. package/dist/esm/core/phone-auth/ui/modal.js +889 -422
  83. package/dist/esm/core/phone-auth/validation-utils.d.ts +0 -31
  84. package/dist/esm/core/phone-auth/validation-utils.js +2 -40
  85. package/dist/esm/core/types.d.ts +0 -35
  86. package/dist/esm/core/version.js +1 -2
  87. package/dist/esm/index.js +1 -9
  88. package/dist/index.js +0 -7
  89. package/package.json +1 -1
@@ -1,52 +1,37 @@
1
- /**
2
- * Modal UI Component for Phone Authentication
3
- *
4
- * This file creates the UI components (modals, buttons) that are shown
5
- * when the SDK is NOT in headless mode. Think of it like a popup window
6
- * that handles the authentication flow for you.
7
- */
8
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
9
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10
- return new (P || (P = Promise))(function (resolve, reject) {
11
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
12
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
13
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
14
- step((generator = generator.apply(thisArg, _arguments || [])).next());
15
- });
16
- };
17
- /**
18
- * Creates and manages a modal dialog for authentication
19
- *
20
- * @example
21
- * const modal = new AuthModal({
22
- * title: "Verify Your Phone",
23
- * description: "Complete authentication to continue"
24
- * });
25
- * modal.show();
26
- */
27
1
  export class AuthModal {
28
2
  constructor(options, callbacks) {
29
3
  this.container = null;
30
4
  this.backdrop = null;
31
5
  this.isOpen = false;
6
+ this.currentStep = 'os-choice';
7
+ this.qrCodeData = null;
8
+ this.statusMessage = '';
9
+ this.originalBodyOverflow = '';
10
+ this.isClosing = false;
11
+ this.iconApple = `<svg class="glide-icon glide-icon-os" viewBox="0 0 384 512" fill="currentColor"><path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 52.3-11.4 69.5-34.3z"/></svg>`;
12
+ this.iconAndroid = `<svg class="glide-icon glide-icon-os" viewBox="0 0 576 512" fill="currentColor"><path d="M420.55,301.93a24,24,0,1,1,24-24,24,24,0,0,1-24,24m-265.1,0a24,24,0,1,1,24-24,24,24,0,0,1-24,24m273.7-144.48,47.94-83a10,10,0,1,0-17.27-10h0l-48.54,84.07a301.25,301.25,0,0,0-246.56,0L116.18,64.45a10,10,0,1,0-17.27,10h0l47.94,83C64.53,202.22,8.24,285.55,0,384H576c-8.24-98.45-64.54-181.78-146.85-226.55"/></svg>`;
13
+ this.iconBack = `<svg class="glide-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>`;
32
14
  this.options = options || {};
33
15
  this.callbacks = callbacks || {};
34
- // Bind escape key handler
16
+ this.theme = this.options.theme || 'auto';
35
17
  this.handleEscapeKey = this.handleEscapeKey.bind(this);
36
18
  }
19
+ shouldUseDarkMode() {
20
+ if (this.theme === 'dark')
21
+ return true;
22
+ if (this.theme === 'light')
23
+ return false;
24
+ return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
25
+ }
37
26
  handleEscapeKey(event) {
38
27
  var _a, _b;
39
28
  if (event.key === 'Escape' && this.isOpen) {
40
- // Check if escape closing is enabled (default true)
41
29
  if (((_a = this.options) === null || _a === void 0 ? void 0 : _a.closeOnEscape) !== false) {
42
- (_b = this.closeCallback) === null || _b === void 0 ? void 0 : _b.call(this); // Call the cancellation callback if set
30
+ (_b = this.closeCallback) === null || _b === void 0 ? void 0 : _b.call(this);
43
31
  this.close();
44
32
  }
45
33
  }
46
34
  }
47
- /**
48
- * Escape HTML to prevent XSS attacks
49
- */
50
35
  escapeHtml(unsafe) {
51
36
  return unsafe
52
37
  .replace(/&/g, "&amp;")
@@ -55,540 +40,1022 @@ export class AuthModal {
55
40
  .replace(/"/g, "&quot;")
56
41
  .replace(/'/g, "&#039;");
57
42
  }
58
- /**
59
- * Shows the modal with a QR code for desktop authentication
60
- * Supports both single QR code (legacy) and dual-platform QR codes (iOS + Android)
61
- */
62
- showQRCode(qrCodeData, statusMessage = 'Scan QR code with your phone') {
63
- console.log('[Modal] showQRCode called with:', qrCodeData);
64
- // If modal is already open, don't recreate it
65
- if (this.isOpen) {
66
- console.log('[Modal] Modal already open, skipping recreation');
67
- return;
43
+ showQRCode(qrCodeData, statusMessage = 'Scan with your phone camera') {
44
+ this.qrCodeData = qrCodeData;
45
+ this.statusMessage = statusMessage;
46
+ const mode = this.options.viewMode || 'toggle';
47
+ if (typeof qrCodeData === 'string') {
48
+ this.renderToggleMode(qrCodeData, statusMessage);
68
49
  }
69
- // Check if it's the new dual-platform format with VALID Android QR code
70
- if (typeof qrCodeData === 'object' && qrCodeData.iosQRCode) {
71
- const hasValidAndroidQR = qrCodeData.androidQRCode &&
72
- qrCodeData.androidQRCode.length > 0 &&
73
- qrCodeData.androidQRCode !== qrCodeData.iosQRCode;
74
- console.log('[Modal] Has valid Android QR?', hasValidAndroidQR);
75
- if (hasValidAndroidQR) {
76
- console.log('[Modal] Using dual-platform modal');
77
- this.createDualPlatformQRModal(qrCodeData, statusMessage);
78
- // Note: createDualPlatformQRModal calls show() internally
79
- return;
50
+ else {
51
+ switch (mode) {
52
+ case 'dual':
53
+ this.renderDualMode(qrCodeData, statusMessage);
54
+ break;
55
+ case 'pre-step':
56
+ this.renderPreStepMode(qrCodeData, statusMessage);
57
+ break;
58
+ case 'toggle':
59
+ default:
60
+ this.renderToggleMode(qrCodeData, statusMessage);
61
+ break;
80
62
  }
81
- else {
82
- console.log('[Modal] Android QR missing/empty, using single iOS QR');
83
- // Only iOS QR code available - show single QR
84
- this.createModal(`
85
- <div class="glide-auth-qr-container">
86
- <img src="${this.escapeHtml(qrCodeData.iosQRCode)}" alt="QR Code" class="glide-auth-qr-code" />
87
- <p class="glide-auth-status">Scan with your iPhone to authenticate</p>
88
- </div>
89
- `);
63
+ }
64
+ this.show();
65
+ }
66
+ updateStatus(status, isError = false) {
67
+ this.statusMessage = status;
68
+ if (this.container) {
69
+ const el = this.container.querySelector('#glide-status');
70
+ if (el) {
71
+ el.textContent = status;
72
+ if (isError) {
73
+ el.style.color = '#ff3b30';
74
+ }
75
+ else {
76
+ el.style.color = '';
77
+ }
90
78
  }
91
79
  }
80
+ }
81
+ setCloseCallback(callback) {
82
+ this.closeCallback = callback;
83
+ }
84
+ renderToggleMode(data, msg) {
85
+ let iosQR = '';
86
+ let androidQR = '';
87
+ if (typeof data === 'string') {
88
+ iosQR = data;
89
+ androidQR = data;
90
+ }
92
91
  else {
93
- console.log('[Modal] Using legacy single QR code modal');
94
- // Legacy single QR code
95
- this.createModal(`
96
- <div class="glide-auth-qr-container">
97
- <img src="${this.escapeHtml(qrCodeData)}" alt="QR Code" class="glide-auth-qr-code" />
98
- <p class="glide-auth-status">${this.escapeHtml(statusMessage)}</p>
99
- </div>
100
- `);
92
+ iosQR = data.iosQRCode;
93
+ androidQR = data.androidQRCode || data.iosQRCode;
101
94
  }
102
- this.show();
95
+ const content = `
96
+ <div class="glide-content">
97
+ <div class="glide-toggle" id="glide-toggle" data-active="ios">
98
+ <div class="glide-toggle-slider"></div>
99
+ <button class="glide-btn glide-toggle-btn active" data-platform="ios">${this.iconApple}<span>iOS</span></button>
100
+ <button class="glide-btn glide-toggle-btn" data-platform="android">${this.iconAndroid}<span>Android</span></button>
101
+ </div>
102
+
103
+ <div class="glide-qr-area">
104
+ <img
105
+ class="glide-qr-img"
106
+ id="glide-qr-img"
107
+ src="${this.escapeHtml(iosQR)}"
108
+ alt="QR Code"
109
+ class="glide-qr-img"
110
+ data-ios="${this.escapeHtml(iosQR)}"
111
+ data-android="${this.escapeHtml(androidQR)}"
112
+ />
113
+
114
+ <!-- Animation Overlay (Inside QR Area for centering) -->
115
+ <div id="glide-phone-overlay" class="glide-phone-animation-overlay">
116
+ <div class="glide-outlined-phone">
117
+ <div class="glide-scan-line"></div>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ `;
123
+ this.createModal(content, '', true);
124
+ this.setupPlatformToggles();
125
+ this.setupHelpInteraction();
103
126
  }
104
- /**
105
- * Creates a modal with iOS/Android platform toggle
106
- */
107
- createDualPlatformQRModal(qrCodeData, statusMessage) {
127
+ renderDualMode(data, msg) {
128
+ const qrOverlayHtml = `
129
+ <div class="glide-phone-animation-overlay">
130
+ <div class="glide-outlined-phone">
131
+ <div class="glide-scan-line"></div>
132
+ </div>
133
+ </div>
134
+ `;
108
135
  this.createModal(`
109
- <div class="glide-auth-qr-container">
110
- <!-- Platform Switcher -->
111
- <div class="glide-platform-switcher">
112
- <button class="glide-platform-btn glide-platform-ios active" data-platform="ios">
113
- <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
114
- <path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"/>
115
- </svg>
116
- <span>iOS</span>
117
- </button>
118
- <button class="glide-platform-btn glide-platform-android" data-platform="android">
119
- <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
120
- <path d="M17.6 9.48l1.84-3.18c.16-.31.04-.69-.26-.85-.29-.15-.65-.06-.83.22l-1.88 3.24c-2.86-1.21-6.08-1.21-8.94 0L5.65 5.67c-.19-.28-.54-.38-.83-.22-.3.16-.42.54-.26.85l1.84 3.18C4.08 11.08 2.4 13.97 2.4 17.2h19.2c0-3.23-1.68-6.12-4.0-7.72zM7.0 14.8c-.66 0-1.2-.54-1.2-1.2s.54-1.2 1.2-1.2 1.2.54 1.2 1.2-.54 1.2-1.2 1.2zm10 0c-.66 0-1.2-.54-1.2-1.2s.54-1.2 1.2-1.2 1.2.54 1.2 1.2-.54 1.2-1.2 1.2z"/>
121
- </svg>
122
- <span>Android</span>
123
- </button>
136
+ <div class="glide-content glide-dual-mode">
137
+ <div class="glide-dual-container">
138
+ <div class="glide-dual-item">
139
+ <div class="glide-os-logo">
140
+ ${this.iconApple}
141
+ <span>iOS</span>
142
+ </div>
143
+ <div class="glide-qr-area">
144
+ <img src="${this.escapeHtml(data.iosQRCode)}" alt="iOS QR" class="glide-qr-img" />
145
+ ${qrOverlayHtml}
146
+ </div>
147
+ </div>
148
+ <div class="glide-dual-separator">
149
+ <div class="glide-separator-line"></div>
150
+ <span class="glide-separator-text">or</span>
151
+ <div class="glide-separator-line"></div>
152
+ </div>
153
+ <div class="glide-dual-item">
154
+ <div class="glide-os-logo">
155
+ ${this.iconAndroid}
156
+ <span>Android</span>
157
+ </div>
158
+ <div class="glide-qr-area">
159
+ <img src="${this.escapeHtml(data.androidQRCode || data.iosQRCode)}" alt="Android QR" class="glide-qr-img" />
160
+ ${qrOverlayHtml}
161
+ </div>
162
+ </div>
124
163
  </div>
125
-
126
- <!-- QR Code Image -->
127
- <img
128
- id="glide-qr-code-img"
129
- src="${this.escapeHtml(qrCodeData.iosQRCode)}"
130
- alt="QR Code"
131
- class="glide-auth-qr-code"
132
- data-ios="${this.escapeHtml(qrCodeData.iosQRCode)}"
133
- data-android="${this.escapeHtml(qrCodeData.androidQRCode || '')}"
134
- />
135
-
136
- <!-- Status Message -->
137
- <p class="glide-auth-status" id="glide-platform-message">Scan with your iPhone to authenticate</p>
138
164
  </div>
139
- `);
140
- // IMPORTANT: Call show() to actually display the modal!
165
+ `, 'glide-modal-wide', true);
166
+ this.setupHelpInteraction();
167
+ }
168
+ renderPreStepMode(data, msg) {
169
+ this.currentStep = 'os-choice';
170
+ const contentHtml = `
171
+ <div class="glide-pre-step-container">
172
+ <button class="glide-os-choice-btn" id="glide-btn-ios">
173
+ ${this.iconApple}
174
+ <span>iOS</span>
175
+ </button>
176
+ <button class="glide-os-choice-btn" id="glide-btn-android">
177
+ ${this.iconAndroid}
178
+ <span>Android</span>
179
+ </button>
180
+ </div>
181
+ `;
182
+ this.createModal(contentHtml, '', false);
183
+ this.setupPreStepListeners();
141
184
  this.show();
142
185
  }
143
- /**
144
- * Sets a callback to be called when the modal is cancelled/closed
145
- */
146
- setCloseCallback(callback) {
147
- this.closeCallback = callback;
186
+ setupPreStepListeners() {
187
+ var _a, _b;
188
+ if (!this.container)
189
+ return;
190
+ (_a = this.container.querySelector('#glide-btn-ios')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', () => {
191
+ this.currentStep = 'ios-qr';
192
+ this.updatePreStepUI();
193
+ });
194
+ (_b = this.container.querySelector('#glide-btn-android')) === null || _b === void 0 ? void 0 : _b.addEventListener('click', () => {
195
+ this.currentStep = 'android-qr';
196
+ this.updatePreStepUI();
197
+ });
148
198
  }
149
- /**
150
- * Shows the modal with a button for Link authentication (App Clips)
151
- * IMPORTANT: The button click is required for iOS to recognize the app link
152
- */
153
- showLinkButton(url, buttonText) {
154
- return new Promise((resolve) => {
155
- var _a, _b, _c;
156
- const text = buttonText || ((_a = this.options) === null || _a === void 0 ? void 0 : _a.buttonText) || 'Open Verification App';
157
- this.createModal(`
158
- <div class="glide-auth-link-container">
159
- <p class="glide-auth-description">
160
- ${((_b = this.options) === null || _b === void 0 ? void 0 : _b.description) || 'Click below to verify your phone number'}
161
- </p>
162
- <button class="glide-auth-button" id="glide-auth-link-button">
163
- ${text}
199
+ updatePreStepUI() {
200
+ var _a, _b, _c;
201
+ if (!this.container)
202
+ return;
203
+ let contentHtml = '';
204
+ if (this.currentStep === 'os-choice') {
205
+ contentHtml = `
206
+ <div class="glide-pre-step-container">
207
+ <button class="glide-os-choice-btn" id="glide-btn-ios">
208
+ ${this.iconApple}
209
+ <span>iOS</span>
210
+ </button>
211
+ <button class="glide-os-choice-btn" id="glide-btn-android">
212
+ ${this.iconAndroid}
213
+ <span>Android</span>
164
214
  </button>
165
- <p class="glide-auth-helper-text">
166
- You'll be redirected to complete verification
167
- </p>
168
215
  </div>
169
- `);
170
- // Show modal first so elements are in the DOM
171
- this.show();
172
- // Add click handler for the button AFTER modal is in DOM
173
- // CRITICAL: This click event is required for iOS to recognize the app link
174
- // Do NOT call window.open automatically - it must be triggered by user interaction
175
- const button = (_c = this.container) === null || _c === void 0 ? void 0 : _c.querySelector('#glide-auth-link-button');
176
- if (button) {
177
- button.addEventListener('click', (event) => {
178
- var _a, _b;
179
- (_b = (_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.onAuthStart) === null || _b === void 0 ? void 0 : _b.call(_a);
180
- // User-initiated click is required for app links to work properly on iOS
181
- // This ensures the OS recognizes it should open an app, not just a browser tab
182
- window.open(url, '_blank');
183
- resolve();
184
- });
185
- }
186
- else {
187
- console.error('[Modal] Link button not found in modal');
188
- }
216
+ `;
217
+ this.createModal(contentHtml, '', false);
218
+ this.setupPreStepListeners();
219
+ }
220
+ else if (this.currentStep === 'ios-qr') {
221
+ const qr = (typeof this.qrCodeData === 'object' && ((_a = this.qrCodeData) === null || _a === void 0 ? void 0 : _a.iosQRCode))
222
+ ? this.qrCodeData.iosQRCode
223
+ : this.qrCodeData;
224
+ contentHtml = `
225
+ <div class="glide-content">
226
+ <div class="glide-qr-area">
227
+ <img src="${this.escapeHtml(qr)}" alt="QR Code" class="glide-qr-img" />
228
+
229
+ <!-- Animation Overlay -->
230
+ <div id="glide-phone-overlay" class="glide-phone-animation-overlay">
231
+ <div class="glide-outlined-phone">
232
+ <div class="glide-scan-line"></div>
233
+ </div>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ `;
238
+ this.createModal(contentHtml, '', true, true);
239
+ this.setupBackButton();
240
+ this.setupHelpInteraction();
241
+ }
242
+ else if (this.currentStep === 'android-qr') {
243
+ const qr = (typeof this.qrCodeData === 'object' && ((_b = this.qrCodeData) === null || _b === void 0 ? void 0 : _b.androidQRCode))
244
+ ? this.qrCodeData.androidQRCode
245
+ : (typeof this.qrCodeData === 'object' && ((_c = this.qrCodeData) === null || _c === void 0 ? void 0 : _c.iosQRCode))
246
+ ? this.qrCodeData.iosQRCode
247
+ : this.qrCodeData;
248
+ contentHtml = `
249
+ <div class="glide-content">
250
+ <div class="glide-qr-area">
251
+ <img src="${this.escapeHtml(qr)}" alt="QR Code" class="glide-qr-img" />
252
+
253
+ <!-- Animation Overlay -->
254
+ <div id="glide-phone-overlay" class="glide-phone-animation-overlay">
255
+ <div class="glide-outlined-phone">
256
+ <div class="glide-scan-line"></div>
257
+ </div>
258
+ </div>
259
+ </div>
260
+ </div>
261
+ `;
262
+ this.createModal(contentHtml, '', true, true);
263
+ this.setupBackButton();
264
+ this.setupHelpInteraction();
265
+ }
266
+ }
267
+ setupBackButton() {
268
+ var _a;
269
+ if (!this.container)
270
+ return;
271
+ (_a = this.container.querySelector('#glide-back-btn')) === null || _a === void 0 ? void 0 : _a.addEventListener('click', () => {
272
+ this.currentStep = 'os-choice';
273
+ this.updatePreStepUI();
189
274
  });
190
275
  }
191
- /**
192
- * Shows the modal with a button for TS43 authentication
193
- * IMPORTANT: The button click is required for Digital Credentials API (transient activation)
194
- */
195
- showTS43Button(onAuthenticate) {
196
- return new Promise((resolve, reject) => {
197
- var _a, _b, _c;
198
- const text = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.buttonText) || 'Verify with Carrier';
199
- this.createModal(`
200
- <div class="glide-auth-ts43-container">
201
- <p class="glide-auth-description">
202
- ${((_b = this.options) === null || _b === void 0 ? void 0 : _b.description) || 'Verify using your carrier credentials'}
203
- </p>
204
- <button class="glide-auth-button" id="glide-auth-ts43-button">
205
- ${text}
206
- </button>
207
- <p class="glide-auth-helper-text">
208
- Secure verification through your mobile carrier
209
- </p>
210
- </div>
211
- `);
212
- // Show modal first so elements are in the DOM
213
- this.show();
214
- // Add click handler for the button AFTER modal is in DOM
215
- // CRITICAL: Digital Credentials API requires "transient activation" (user interaction)
216
- // Do NOT call onAuthenticate automatically - it must be triggered by user click
217
- const button = (_c = this.container) === null || _c === void 0 ? void 0 : _c.querySelector('#glide-auth-ts43-button');
218
- if (button) {
219
- button.addEventListener('click', (event) => __awaiter(this, void 0, void 0, function* () {
220
- var _a, _b, _c, _d, _e, _f;
221
- (_b = (_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.onAuthStart) === null || _b === void 0 ? void 0 : _b.call(_a);
222
- button.setAttribute('disabled', 'true');
223
- button.textContent = 'Verifying...';
224
- try {
225
- const result = yield onAuthenticate();
226
- (_d = (_c = this.callbacks) === null || _c === void 0 ? void 0 : _c.onAuthComplete) === null || _d === void 0 ? void 0 : _d.call(_c, result);
227
- resolve(result);
228
- this.close();
229
- }
230
- catch (error) {
231
- (_f = (_e = this.callbacks) === null || _e === void 0 ? void 0 : _e.onError) === null || _f === void 0 ? void 0 : _f.call(_e, error);
232
- button.removeAttribute('disabled');
233
- button.textContent = text;
234
- reject(error);
235
- }
236
- }));
276
+ createModal(content, extraClass = '', includeHelpButton = false, includeBackButton = false) {
277
+ var _a, _b, _c, _d, _e;
278
+ if (this.isClosing) {
279
+ this.isClosing = false;
280
+ this.cleanup();
281
+ }
282
+ if (!this.container) {
283
+ this.backdrop = document.createElement('div');
284
+ this.backdrop.className = 'glide-backdrop';
285
+ this.backdrop.id = 'glide-backdrop';
286
+ this.container = document.createElement('div');
287
+ this.container.className = `glide-modal ${((_a = this.options) === null || _a === void 0 ? void 0 : _a.className) || ''} ${extraClass}`;
288
+ this.container.id = 'glide-modal';
289
+ if (this.shouldUseDarkMode()) {
290
+ this.container.classList.add('dark');
237
291
  }
238
- else {
239
- console.error('[Modal] TS43 button not found in modal');
292
+ if (this.isOpen) {
293
+ document.body.appendChild(this.backdrop);
294
+ document.body.appendChild(this.container);
295
+ }
296
+ }
297
+ else {
298
+ this.container.className = `glide-modal ${((_b = this.options) === null || _b === void 0 ? void 0 : _b.className) || ''} ${extraClass}`;
299
+ if (this.shouldUseDarkMode()) {
300
+ this.container.classList.add('dark');
240
301
  }
241
- });
242
- }
243
- /**
244
- * Updates the status message in the modal
245
- */
246
- updateStatus(message, isError = false) {
247
- var _a;
248
- const statusEl = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('.glide-auth-status');
249
- if (statusEl) {
250
- statusEl.textContent = message;
251
- statusEl.className = isError ? 'glide-auth-status glide-auth-error' : 'glide-auth-status';
252
302
  }
253
- }
254
- /**
255
- * Creates the modal HTML structure
256
- */
257
- createModal(content) {
258
- var _a, _b, _c;
259
- // Clean up any existing modal
260
- this.cleanup();
261
- // Create backdrop (dark overlay)
262
- this.backdrop = document.createElement('div');
263
- this.backdrop.className = 'glide-auth-backdrop';
264
- this.backdrop.style.cssText = `
265
- position: fixed;
266
- top: 0;
267
- left: 0;
268
- right: 0;
269
- bottom: 0;
270
- background: rgba(0, 0, 0, 0.5);
271
- z-index: 9998;
272
- opacity: 0;
273
- transition: opacity 0.3s ease;
274
- `;
275
- // Create modal container
276
- this.container = document.createElement('div');
277
- this.container.className = `glide-auth-modal ${((_a = this.options) === null || _a === void 0 ? void 0 : _a.className) || ''}`;
278
- this.container.style.cssText = `
279
- position: fixed;
280
- top: 50%;
281
- left: 50%;
282
- transform: translate(-50%, -50%) scale(0.9);
283
- background: white;
284
- border-radius: 12px;
285
- padding: 24px;
286
- max-width: 400px;
287
- width: 90%;
288
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
289
- z-index: 9999;
290
- opacity: 0;
291
- transition: opacity 0.3s ease, transform 0.3s ease;
292
- `;
293
- // Add modal content
294
303
  this.container.innerHTML = `
295
- <div class="glide-auth-header">
296
- ${((_b = this.options) === null || _b === void 0 ? void 0 : _b.showCloseButton) !== false ? `
297
- <button class="glide-auth-close" aria-label="Close">
298
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
299
- <line x1="18" y1="6" x2="6" y2="18"></line>
300
- <line x1="6" y1="6" x2="18" y2="18"></line>
301
- </svg>
302
- </button>
303
- ` : ''}
304
- <h2 class="glide-auth-title">${((_c = this.options) === null || _c === void 0 ? void 0 : _c.title) || 'Phone Verification'}</h2>
305
- </div>
306
- <div class="glide-auth-content">
307
- ${content}
308
- </div>
304
+ ${includeBackButton ? `
305
+ <button class="glide-btn glide-btn-back" id="glide-back-btn" aria-label="Back">
306
+ ${this.iconBack}
307
+ </button>
308
+ ` : ''}
309
+ ${((_c = this.options) === null || _c === void 0 ? void 0 : _c.showCloseButton) !== false ? `
310
+ <button class="glide-btn glide-btn-close" id="glide-close-btn" aria-label="Close">
311
+ <svg class="glide-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
312
+ <line x1="18" y1="6" x2="6" y2="18"></line>
313
+ <line x1="6" y1="6" x2="18" y2="18"></line>
314
+ </svg>
315
+ </button>
316
+ ` : ''}
317
+ <h2 class="glide-title">${((_d = this.options) === null || _d === void 0 ? void 0 : _d.title) || 'Scan to Verify'}</h2>
318
+ <div id="glide-modal-content">${content}</div>
319
+ <p class="glide-status" id="glide-status">${((_e = this.options) === null || _e === void 0 ? void 0 : _e.description) || ''}</p>
320
+ ${includeHelpButton ? `
321
+ <button class="glide-btn glide-btn-help" id="glide-help-btn">?</button>
322
+ ` : ''}
309
323
  `;
310
- // Add styles
311
324
  this.injectStyles();
312
- // Add close button handler
313
- const closeBtn = this.container.querySelector('.glide-auth-close');
325
+ const closeBtn = this.container.querySelector('#glide-close-btn');
314
326
  if (closeBtn) {
315
327
  closeBtn.addEventListener('click', () => {
316
328
  var _a;
317
- (_a = this.closeCallback) === null || _a === void 0 ? void 0 : _a.call(this); // Call cancellation callback if set
329
+ (_a = this.closeCallback) === null || _a === void 0 ? void 0 : _a.call(this);
318
330
  this.close();
319
331
  });
320
332
  }
321
- // Add backdrop click handler
322
- this.backdrop.addEventListener('click', (e) => {
323
- var _a, _b;
324
- // Only close if backdrop itself was clicked and closeOnBackdrop is enabled (default true)
325
- if (e.target === this.backdrop && ((_a = this.options) === null || _a === void 0 ? void 0 : _a.closeOnBackdrop) !== false) {
326
- (_b = this.closeCallback) === null || _b === void 0 ? void 0 : _b.call(this); // Call cancellation callback if set
327
- this.close();
328
- }
329
- });
333
+ if (this.backdrop) {
334
+ this.backdrop.onclick = (e) => {
335
+ var _a, _b;
336
+ if (e.target === this.backdrop && ((_a = this.options) === null || _a === void 0 ? void 0 : _a.closeOnBackdrop) !== false) {
337
+ (_b = this.closeCallback) === null || _b === void 0 ? void 0 : _b.call(this);
338
+ this.close();
339
+ }
340
+ };
341
+ }
342
+ }
343
+ setupHelpInteraction() {
344
+ var _a;
345
+ const trigger = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('#glide-help-btn');
346
+ if (trigger) {
347
+ trigger.addEventListener('click', () => {
348
+ var _a;
349
+ const overlays = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.glide-phone-animation-overlay');
350
+ if (overlays) {
351
+ overlays.forEach(overlay => {
352
+ overlay.classList.remove('playing');
353
+ void overlay.offsetWidth;
354
+ overlay.classList.add('playing');
355
+ setTimeout(() => {
356
+ overlay.classList.remove('playing');
357
+ }, 3000);
358
+ });
359
+ }
360
+ });
361
+ }
330
362
  }
331
- /**
332
- * Injects CSS styles for the modal
333
- */
334
363
  injectStyles() {
335
- if (document.getElementById('glide-auth-styles'))
364
+ if (document.getElementById('glide-modal-styles'))
336
365
  return;
337
366
  const styles = document.createElement('style');
338
- styles.id = 'glide-auth-styles';
367
+ styles.id = 'glide-modal-styles';
339
368
  styles.textContent = `
340
- .glide-auth-backdrop {
341
- backdrop-filter: blur(4px);
369
+ :root {
370
+ --glide-primary: #007AFF;
371
+ --glide-text: #1d1d1f;
372
+ --glide-bg-light: rgba(255, 255, 255, 0.6);
373
+ --glide-bg-dark: rgba(30, 30, 30, 0.6);
374
+ --glide-phone-border: #000000; /* High Contrast Black */
375
+
376
+ /* Button sizes */
377
+ --glide-btn-size: 28px;
378
+ --glide-help-btn-size: 24px;
379
+ --glide-toggle-icon-size: 14px;
380
+ --glide-dual-icon-size: 18px;
381
+ }
382
+
383
+ #glide-backdrop {
384
+ position: fixed;
385
+ top: 0;
386
+ left: 0;
387
+ right: 0;
388
+ bottom: 0;
389
+ background: rgba(0, 0, 0, 0.2);
390
+ backdrop-filter: blur(8px);
391
+ -webkit-backdrop-filter: blur(8px);
392
+ z-index: 9998;
393
+ opacity: 0;
394
+ transition: opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1);
342
395
  }
343
396
 
344
- .glide-auth-modal {
345
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
397
+ #glide-modal {
398
+ position: fixed;
399
+ top: 20px;
400
+ left: 50%;
401
+ transform: translateX(-50%) scale(0.94);
402
+ background: var(--glide-bg-light);
403
+ backdrop-filter: blur(24px) saturate(180%);
404
+ -webkit-backdrop-filter: blur(24px) saturate(180%);
405
+ border-radius: 24px;
406
+ padding: 32px;
407
+ width: 360px;
408
+ max-width: 90%;
409
+ box-shadow:
410
+ 0 20px 40px rgba(0,0,0,0.2),
411
+ 0 0 0 1px rgba(255,255,255,0.6) inset,
412
+ 0 0 0 1px rgba(0,0,0,0.05);
413
+ z-index: 9999;
414
+ opacity: 0;
415
+ transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
416
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
417
+ text-align: center;
418
+ color: var(--glide-text);
419
+ }
420
+
421
+ #glide-modal.glide-modal-wide {
422
+ width: 600px;
423
+ max-width: 95vw;
346
424
  }
347
425
 
348
- .glide-auth-header {
349
- position: relative;
350
- margin-bottom: 20px;
426
+ #glide-modal.dark {
427
+ background: var(--glide-bg-dark);
428
+ color: white;
429
+ box-shadow:
430
+ 0 20px 40px rgba(0,0,0,0.4),
431
+ 0 0 0 1px rgba(255,255,255,0.15) inset,
432
+ 0 0 0 1px rgba(0,0,0,0.5);
433
+ --glide-phone-border: #ffffff; /* High Contrast White */
351
434
  }
352
435
 
353
- .glide-auth-close {
436
+ #glide-modal .glide-btn-close {
354
437
  position: absolute;
355
- top: 0;
356
- right: 0;
357
- background: none;
438
+ top: 16px;
439
+ right: 16px;
440
+ width: var(--glide-btn-size);
441
+ height: var(--glide-btn-size);
442
+ min-width: var(--glide-btn-size);
443
+ min-height: var(--glide-btn-size);
444
+ max-width: var(--glide-btn-size);
445
+ max-height: var(--glide-btn-size);
446
+ background: rgba(118, 118, 128, 0.12);
358
447
  border: none;
448
+ border-radius: 50%;
449
+ display: flex;
450
+ align-items: center;
451
+ justify-content: center;
359
452
  cursor: pointer;
453
+ color: rgba(0,0,0,0.5);
454
+ transition: background 0.2s;
360
455
  padding: 0;
361
- color: #666;
362
- transition: color 0.2s;
456
+ z-index: 20;
457
+ box-sizing: border-box;
458
+ flex-shrink: 0;
363
459
  }
364
460
 
365
- .glide-auth-close:hover {
366
- color: #000;
461
+ #glide-modal.dark .glide-btn-close {
462
+ background: rgba(255, 255, 255, 0.1);
463
+ color: rgba(255,255,255,0.5);
367
464
  }
368
465
 
369
- .glide-auth-title {
370
- margin: 0;
371
- font-size: 24px;
372
- font-weight: 600;
373
- color: #333;
374
- text-align: center;
466
+ #glide-modal .glide-btn-close:hover {
467
+ background: rgba(118, 118, 128, 0.2);
468
+ }
469
+
470
+ #glide-modal.dark .glide-btn-close:hover {
471
+ background: rgba(255, 255, 255, 0.2);
375
472
  }
376
473
 
377
- .glide-auth-content {
474
+ .glide-title {
475
+ margin: 0 0 8px 0;
476
+ font-size: 22px;
477
+ font-weight: 600;
478
+ letter-spacing: -0.01em;
479
+ }
480
+
481
+ .glide-status {
482
+ margin: 12px 0 0 0;
483
+ font-size: 13px;
484
+ color: rgba(0,0,0,0.5);
378
485
  text-align: center;
486
+ min-height: 18px;
487
+ }
488
+
489
+ .glide-status:empty {
490
+ display: none;
491
+ }
492
+
493
+ #glide-modal.dark .glide-status {
494
+ color: rgba(255,255,255,0.5);
495
+ }
496
+
497
+ .glide-content {
498
+ display: flex;
499
+ flex-direction: column;
500
+ align-items: center;
501
+ position: relative;
502
+ }
503
+
504
+ /* --- Sliding Toggle Switch --- */
505
+ #glide-toggle {
506
+ background: rgba(118, 118, 128, 0.12);
507
+ padding: 2px;
508
+ border-radius: 8px;
509
+ display: inline-flex;
510
+ margin-bottom: 24px;
511
+ margin-top: 16px;
512
+ position: relative;
513
+ height: 32px;
514
+ width: 200px;
515
+ box-sizing: border-box;
379
516
  }
380
517
 
381
- .glide-auth-description {
382
- color: #666;
383
- margin: 0 0 20px 0;
384
- font-size: 16px;
385
- line-height: 1.5;
518
+ #glide-modal.dark #glide-toggle {
519
+ background: rgba(118, 118, 128, 0.24);
520
+ }
521
+
522
+ /* The sliding background */
523
+ .glide-toggle-slider {
524
+ position: absolute;
525
+ top: 2px;
526
+ left: 2px;
527
+ width: calc(50% - 2px);
528
+ height: calc(100% - 4px);
529
+ background: white;
530
+ border-radius: 6px;
531
+ box-shadow: 0 3px 8px rgba(0,0,0,0.12), 0 3px 1px rgba(0,0,0,0.04);
532
+ transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
533
+ z-index: 0;
534
+ }
535
+
536
+ #glide-modal.dark .glide-toggle-slider {
537
+ background: #636366;
538
+ }
539
+
540
+ /* Move slider for Android */
541
+ #glide-toggle[data-active="android"] .glide-toggle-slider {
542
+ transform: translateX(100%);
386
543
  }
387
544
 
388
- .glide-auth-button {
389
- background: #007AFF;
390
- color: white;
545
+ .glide-toggle-btn {
546
+ flex: 1;
547
+ background: none;
391
548
  border: none;
392
- border-radius: 8px;
393
- padding: 12px 24px;
394
- font-size: 16px;
549
+ padding: 0;
550
+ margin: 0;
551
+ font-size: 13px;
395
552
  font-weight: 500;
553
+ color: inherit;
396
554
  cursor: pointer;
397
- transition: background 0.2s, transform 0.1s;
398
- min-width: 200px;
555
+ position: relative;
556
+ z-index: 1;
557
+ transition: color 0.2s;
558
+ display: flex;
559
+ align-items: center;
560
+ justify-content: center;
561
+ gap: 6px;
562
+ min-height: auto;
563
+ height: auto;
564
+ min-width: auto;
565
+ border-radius: 0;
399
566
  }
400
-
401
- .glide-auth-button:hover:not(:disabled) {
402
- background: #0051D5;
403
- transform: translateY(-1px);
567
+
568
+ #glide-modal .glide-toggle-btn .glide-icon-os,
569
+ #glide-modal .glide-toggle-btn svg {
570
+ width: var(--glide-toggle-icon-size);
571
+ height: var(--glide-toggle-icon-size);
572
+ flex-shrink: 0;
573
+ margin: 0;
574
+ padding: 0;
404
575
  }
405
-
406
- .glide-auth-button:active {
407
- transform: translateY(0);
576
+
577
+ .glide-toggle-btn span {
578
+ margin: 0;
579
+ padding: 0;
580
+ line-height: 1;
408
581
  }
409
-
410
- .glide-auth-button:disabled {
411
- opacity: 0.6;
412
- cursor: not-allowed;
582
+
583
+ #glide-modal.dark .glide-toggle-btn {
584
+ color: rgba(255,255,255,0.6);
585
+ }
586
+
587
+ #glide-toggle[data-active="ios"] .glide-toggle-btn[data-platform="ios"],
588
+ #glide-toggle[data-active="android"] .glide-toggle-btn[data-platform="android"] {
589
+ color: #1d1d1f;
590
+ }
591
+
592
+ #glide-modal.dark #glide-toggle[data-active="ios"] .glide-toggle-btn[data-platform="ios"],
593
+ #glide-modal.dark #glide-toggle[data-active="android"] .glide-toggle-btn[data-platform="android"] {
594
+ color: white;
413
595
  }
414
596
 
415
- .glide-auth-helper-text {
416
- color: #999;
417
- font-size: 14px;
418
- margin: 16px 0 0 0;
597
+ /* QR Area */
598
+ .glide-qr-area {
599
+ position: relative;
600
+ margin: 0 auto;
419
601
  }
420
602
 
421
- .glide-auth-qr-code {
422
- max-width: 256px;
423
- width: 100%;
424
- height: auto;
425
- margin: 20px auto;
603
+ .glide-qr-img {
604
+ width: 200px;
605
+ height: 200px;
606
+ object-fit: contain;
426
607
  display: block;
608
+ border-radius: 16px;
609
+ }
610
+
611
+ /* Dual Mode QR Area - no extra padding, same as single mode */
612
+ .glide-dual-mode .glide-qr-area {
613
+ background: transparent;
614
+ padding: 0;
615
+ }
616
+
617
+ .glide-dual-mode .glide-qr-img {
618
+ width: 180px;
619
+ height: 180px;
620
+ border-radius: 16px;
621
+ }
622
+
623
+ /* --- Help Icon & Interaction --- */
624
+ .glide-btn-help {
625
+ position: absolute;
626
+ bottom: 20px;
627
+ right: 20px;
628
+ width: var(--glide-help-btn-size);
629
+ height: var(--glide-help-btn-size);
630
+ min-width: var(--glide-help-btn-size);
631
+ min-height: var(--glide-help-btn-size);
632
+ max-width: var(--glide-help-btn-size);
633
+ max-height: var(--glide-help-btn-size);
634
+ border-radius: 50%;
635
+ border: 1.5px solid rgba(0,0,0,0.2);
636
+ color: rgba(0,0,0,0.4);
637
+ display: flex;
638
+ align-items: center;
639
+ justify-content: center;
640
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
641
+ font-size: 14px;
642
+ font-weight: 600;
643
+ line-height: 1;
644
+ cursor: pointer;
645
+ transition: all 0.2s;
646
+ background: transparent;
647
+ padding: 0;
648
+ margin: 0;
649
+ box-sizing: border-box;
650
+ z-index: 10;
651
+ }
652
+
653
+ #glide-modal.dark .glide-btn-help {
654
+ border-color: rgba(255,255,255,0.3);
655
+ color: rgba(255,255,255,0.5);
427
656
  }
428
657
 
429
- .glide-auth-status {
430
- color: #666;
431
- font-size: 16px;
432
- margin: 16px 0;
658
+ .glide-btn-help:hover {
659
+ border-color: var(--glide-primary);
660
+ color: var(--glide-primary);
661
+ background: rgba(0, 122, 255, 0.1);
662
+ }
663
+
664
+ /* Tooltip */
665
+ .glide-btn-help::after {
666
+ content: "Need help?";
667
+ position: absolute;
668
+ bottom: 100%;
669
+ left: 50%;
670
+ transform: translateX(-50%) translateY(-8px);
671
+ background: rgba(0,0,0,0.8);
672
+ color: white;
673
+ padding: 4px 8px;
674
+ border-radius: 4px;
675
+ font-size: 11px;
676
+ white-space: nowrap;
677
+ opacity: 0;
678
+ pointer-events: none;
679
+ transition: opacity 0.2s;
680
+ }
681
+
682
+ .glide-btn-help:hover::after {
683
+ opacity: 1;
433
684
  }
434
685
 
435
- .glide-auth-error {
436
- color: #FF3B30;
686
+ /* --- Outlined Phone Animation --- */
687
+ .glide-phone-animation-overlay {
688
+ position: absolute;
689
+ top: 0;
690
+ left: 0;
691
+ width: 100%;
692
+ height: 100%;
693
+ display: flex;
694
+ align-items: center;
695
+ justify-content: center;
696
+ pointer-events: none;
697
+ opacity: 0;
698
+ transition: opacity 0.3s;
699
+ border-radius: 16px;
700
+ background: rgba(255,255,255,0.8);
701
+ }
702
+
703
+ #glide-modal.dark .glide-phone-animation-overlay {
704
+ background: rgba(0,0,0,0.6);
705
+ }
706
+
707
+ .glide-phone-animation-overlay.playing {
708
+ opacity: 1;
437
709
  }
438
710
 
439
- .glide-auth-qr-container,
440
- .glide-auth-link-container,
441
- .glide-auth-ts43-container {
711
+ .glide-outlined-phone {
712
+ width: 100px;
713
+ height: 180px;
714
+ border: 4px solid var(--glide-phone-border);
715
+ border-radius: 16px;
716
+ position: relative;
717
+ background: transparent;
718
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
719
+ transform: translateY(20px);
720
+ }
721
+
722
+ /* Notch */
723
+ .glide-outlined-phone::before {
724
+ content: '';
725
+ position: absolute;
726
+ top: -1px;
727
+ left: 50%;
728
+ transform: translateX(-50%);
729
+ width: 40%;
730
+ height: 12px;
731
+ background: var(--glide-phone-border);
732
+ border-bottom-left-radius: 8px;
733
+ border-bottom-right-radius: 8px;
734
+ }
735
+
736
+ /* Scanning Line */
737
+ .glide-scan-line {
738
+ position: absolute;
739
+ top: 10%;
740
+ left: 5%;
741
+ width: 90%;
742
+ height: 2px;
743
+ background: var(--glide-primary);
744
+ box-shadow: 0 0 8px var(--glide-primary);
745
+ opacity: 0;
746
+ }
747
+
748
+ /* Animation Keyframes */
749
+ @keyframes glide-scan-motion {
750
+ 0% { top: 10%; opacity: 0; }
751
+ 10% { opacity: 1; }
752
+ 90% { opacity: 1; }
753
+ 100% { top: 90%; opacity: 0; }
754
+ }
755
+
756
+ .glide-phone-animation-overlay.playing .glide-outlined-phone {
757
+ animation: glide-phone-appear 3s ease-in-out forwards;
758
+ }
759
+
760
+ .glide-phone-animation-overlay.playing .glide-scan-line {
761
+ animation: glide-scan-motion 2s ease-in-out 0.5s infinite;
762
+ }
763
+
764
+ @keyframes glide-phone-appear {
765
+ 0% { transform: translateY(20px); opacity: 0; }
766
+ 10% { transform: translateY(0); opacity: 1; }
767
+ 90% { transform: translateY(0); opacity: 1; }
768
+ 100% { transform: translateY(20px); opacity: 0; }
769
+ }
770
+
771
+ /* Dual Mode */
772
+ .glide-dual-container {
773
+ display: flex;
774
+ justify-content: center;
775
+ align-items: stretch;
776
+ gap: 32px;
777
+ margin-top: 20px;
778
+ }
779
+
780
+ .glide-dual-item {
781
+ display: flex;
782
+ flex-direction: column;
783
+ align-items: center;
784
+ }
785
+
786
+ .glide-os-logo {
787
+ display: flex;
788
+ align-items: center;
789
+ gap: 8px;
790
+ font-weight: 600;
791
+ margin-bottom: 12px;
792
+ font-size: 15px;
793
+ color: inherit;
794
+ }
795
+
796
+ /* Dual mode OS logo - aligned icons with text, spacing to QR */
797
+ .glide-dual-mode .glide-os-logo {
798
+ margin-bottom: 12px; /* Space between label and QR code */
799
+ }
800
+
801
+ #glide-modal .glide-dual-mode .glide-os-logo .glide-icon-os,
802
+ #glide-modal .glide-dual-mode .glide-os-logo svg {
803
+ width: var(--glide-dual-icon-size);
804
+ height: var(--glide-dual-icon-size);
805
+ margin: 0;
806
+ padding: 0;
807
+ flex-shrink: 0;
808
+ }
809
+
810
+ .glide-dual-mode .glide-os-logo span {
811
+ margin: 0;
812
+ padding: 0;
813
+ line-height: 1;
814
+ }
815
+
816
+ .glide-os-logo svg {
817
+ width: 20px;
818
+ height: 20px;
819
+ opacity: 0.8;
820
+ }
821
+
822
+ /* Dual Mode Separator - aligned to QR codes only */
823
+ .glide-dual-separator {
824
+ display: flex;
825
+ flex-direction: column;
826
+ align-items: center;
827
+ justify-content: center;
828
+ align-self: stretch;
829
+ /* Offset for OS label (18px icon + 12px margin = ~30px) */
830
+ margin-top: 30px;
442
831
  padding: 20px 0;
443
832
  }
444
-
445
- /* Platform Switcher Styles */
446
- .glide-platform-switcher {
833
+
834
+ .glide-separator-line {
835
+ width: 1px;
836
+ flex: 1;
837
+ background: linear-gradient(to bottom, transparent, rgba(128,128,128,0.3), transparent);
838
+ }
839
+
840
+ .glide-separator-text {
841
+ padding: 8px 0;
842
+ font-size: 11px;
843
+ font-weight: 500;
844
+ color: rgba(128,128,128,0.5);
845
+ text-transform: uppercase;
846
+ letter-spacing: 0.5px;
847
+ }
848
+
849
+ #glide-modal.dark .glide-separator-line {
850
+ background: linear-gradient(to bottom, transparent, rgba(255,255,255,0.2), transparent);
851
+ }
852
+
853
+ #glide-modal.dark .glide-separator-text {
854
+ color: rgba(255,255,255,0.4);
855
+ }
856
+
857
+ /* Pre-Step */
858
+ .glide-pre-step-container {
447
859
  display: flex;
860
+ gap: 20px;
448
861
  justify-content: center;
449
- gap: 10px;
450
- margin-bottom: 20px;
862
+ margin: 24px 0;
451
863
  }
452
-
453
- .glide-platform-btn {
864
+
865
+ .glide-os-choice-btn {
454
866
  display: flex;
867
+ flex-direction: column;
455
868
  align-items: center;
456
- gap: 6px;
457
- padding: 10px 20px;
458
- border: 2px solid;
459
- background: transparent;
460
- border-radius: 8px;
869
+ justify-content: center;
870
+ width: 140px;
871
+ height: 140px;
872
+ border: 1px solid rgba(0,0,0,0.08);
873
+ border-radius: 20px;
874
+ background: rgba(255,255,255,0.4);
461
875
  cursor: pointer;
876
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
877
+ position: relative;
878
+ overflow: hidden;
462
879
  font-size: 14px;
463
- font-weight: 600;
464
- transition: all 0.2s ease;
880
+ color: inherit;
465
881
  }
466
-
467
- .glide-platform-btn svg {
882
+
883
+ #glide-modal.dark .glide-os-choice-btn {
884
+ background: rgba(255,255,255,0.05);
885
+ border-color: rgba(255,255,255,0.1);
886
+ color: white;
887
+ }
888
+
889
+ .glide-os-choice-btn:hover {
890
+ background: rgba(255,255,255,0.9);
891
+ transform: translateY(-4px);
892
+ box-shadow: 0 12px 24px rgba(0,0,0,0.1), 0 4px 8px rgba(0,0,0,0.04);
893
+ border-color: var(--glide-primary);
894
+ }
895
+
896
+ #glide-modal.dark .glide-os-choice-btn:hover {
897
+ background: rgba(255,255,255,0.15);
898
+ box-shadow: 0 12px 24px rgba(0,0,0,0.3);
899
+ border-color: var(--glide-primary);
900
+ }
901
+
902
+ .glide-icon-os {
903
+ width: 40px;
904
+ height: 40px;
905
+ margin-bottom: 12px;
906
+ transition: transform 0.3s;
907
+ fill: currentColor;
908
+ }
909
+
910
+ .glide-os-choice-btn:hover .glide-icon-os {
911
+ transform: scale(1.1);
912
+ }
913
+
914
+ #glide-modal .glide-btn-back {
915
+ position: absolute;
916
+ top: 16px;
917
+ left: 16px;
918
+ width: var(--glide-btn-size);
919
+ height: var(--glide-btn-size);
920
+ min-width: var(--glide-btn-size);
921
+ min-height: var(--glide-btn-size);
922
+ max-width: var(--glide-btn-size);
923
+ max-height: var(--glide-btn-size);
924
+ background: rgba(118, 118, 128, 0.12);
925
+ border: none;
926
+ border-radius: 50%;
927
+ display: flex;
928
+ align-items: center;
929
+ justify-content: center;
930
+ cursor: pointer;
931
+ color: rgba(0,0,0,0.5);
932
+ transition: background 0.2s;
933
+ z-index: 20;
934
+ padding: 0;
935
+ margin: 0;
936
+ box-sizing: border-box;
468
937
  flex-shrink: 0;
469
938
  }
470
-
471
- .glide-platform-ios {
472
- border-color: #007AFF;
473
- color: #007AFF;
939
+
940
+ #glide-modal.dark .glide-btn-back {
941
+ background: rgba(255, 255, 255, 0.1);
942
+ color: rgba(255,255,255,0.5);
474
943
  }
475
944
 
476
- .glide-platform-ios.active {
477
- background: #007AFF;
478
- color: white;
945
+ .glide-btn-back:hover {
946
+ background: rgba(118, 118, 128, 0.2);
479
947
  }
480
-
481
- .glide-platform-ios:hover:not(.active) {
482
- background: rgba(0, 122, 255, 0.1);
948
+
949
+ #glide-modal.dark .glide-btn-back:hover {
950
+ background: rgba(255, 255, 255, 0.2);
483
951
  }
484
-
485
- .glide-platform-android {
486
- border-color: #3DDC84;
487
- color: #3DDC84;
952
+
953
+ .glide-spinner {
954
+ width: 40px;
955
+ height: 40px;
956
+ border: 3px solid rgba(0,0,0,0.1);
957
+ border-top-color: var(--glide-primary);
958
+ border-radius: 50%;
959
+ animation: glide-spin 1s linear infinite;
960
+ margin: 40px auto;
488
961
  }
489
-
490
- .glide-platform-android.active {
491
- background: #3DDC84;
492
- color: white;
962
+
963
+ #glide-modal.dark .glide-spinner {
964
+ border-color: rgba(255,255,255,0.1);
965
+ border-top-color: var(--glide-primary);
493
966
  }
494
-
495
- .glide-platform-android:hover:not(.active) {
496
- background: rgba(61, 220, 132, 0.1);
967
+
968
+ @keyframes glide-spin {
969
+ to { transform: rotate(360deg); }
497
970
  }
498
971
  `;
499
972
  document.head.appendChild(styles);
500
973
  }
501
- /**
502
- * Shows the modal with animation
503
- */
504
974
  show() {
505
975
  var _a, _b;
506
- if (!this.container || !this.backdrop || this.isOpen)
976
+ if (!this.container || !this.backdrop || this.isOpen) {
977
+ if (this.isOpen && this.container && this.backdrop) {
978
+ document.removeEventListener('keydown', this.handleEscapeKey);
979
+ document.addEventListener('keydown', this.handleEscapeKey);
980
+ }
507
981
  return;
982
+ }
508
983
  document.body.appendChild(this.backdrop);
509
984
  document.body.appendChild(this.container);
510
- // Add escape key listener for closing modal
985
+ this.lockBodyScroll();
511
986
  document.addEventListener('keydown', this.handleEscapeKey);
512
- // Setup platform toggle handlers if they exist
513
- this.setupPlatformToggles();
514
- // Trigger animation
515
987
  requestAnimationFrame(() => {
516
988
  if (this.backdrop && this.container) {
517
989
  this.backdrop.style.opacity = '1';
518
990
  this.container.style.opacity = '1';
519
- this.container.style.transform = 'translate(-50%, -50%) scale(1)';
991
+ this.container.style.transform = 'translateX(-50%) scale(1)';
520
992
  }
521
993
  });
522
994
  this.isOpen = true;
523
995
  (_b = (_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.onOpen) === null || _b === void 0 ? void 0 : _b.call(_a);
524
996
  }
525
- /**
526
- * Setup click handlers for iOS/Android platform toggle
527
- */
528
997
  setupPlatformToggles() {
529
- var _a, _b, _c;
530
- const platformBtns = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.glide-platform-btn');
531
- const qrImg = (_b = this.container) === null || _b === void 0 ? void 0 : _b.querySelector('#glide-qr-code-img');
532
- const message = (_c = this.container) === null || _c === void 0 ? void 0 : _c.querySelector('#glide-platform-message');
998
+ var _a, _b, _c, _d;
999
+ const container = (_a = this.container) === null || _a === void 0 ? void 0 : _a.querySelector('#glide-toggle');
1000
+ const platformBtns = (_b = this.container) === null || _b === void 0 ? void 0 : _b.querySelectorAll('.glide-toggle-btn');
1001
+ const qrImg = (_c = this.container) === null || _c === void 0 ? void 0 : _c.querySelector('#glide-qr-img');
1002
+ const message = (_d = this.container) === null || _d === void 0 ? void 0 : _d.querySelector('#glide-platform-message');
533
1003
  if (!platformBtns || !qrImg)
534
1004
  return;
535
1005
  platformBtns.forEach((btn) => {
536
1006
  btn.addEventListener('click', (e) => {
537
1007
  const target = e.currentTarget;
538
1008
  const platform = target.getAttribute('data-platform');
539
- // Update active state
1009
+ if (container && platform) {
1010
+ container.setAttribute('data-active', platform);
1011
+ }
540
1012
  platformBtns.forEach(b => b.classList.remove('active'));
541
1013
  target.classList.add('active');
542
- // Switch QR code
543
1014
  if (platform === 'ios') {
544
1015
  qrImg.src = qrImg.getAttribute('data-ios') || '';
545
1016
  if (message)
546
- message.textContent = 'Scan with your iPhone to authenticate';
1017
+ message.textContent = 'Scan with your iPhone camera';
547
1018
  }
548
1019
  else if (platform === 'android') {
549
1020
  qrImg.src = qrImg.getAttribute('data-android') || '';
550
1021
  if (message)
551
- message.textContent = 'Scan with your Android device to authenticate';
1022
+ message.textContent = 'Scan with your Android camera';
552
1023
  }
553
1024
  });
554
1025
  });
555
1026
  }
556
- /**
557
- * Closes the modal with animation
558
- */
1027
+ lockBodyScroll() {
1028
+ this.originalBodyOverflow = document.body.style.overflow;
1029
+ document.body.style.overflow = 'hidden';
1030
+ }
1031
+ unlockBodyScroll() {
1032
+ document.body.style.overflow = this.originalBodyOverflow;
1033
+ }
559
1034
  close() {
560
1035
  if (!this.container || !this.backdrop || !this.isOpen)
561
1036
  return;
562
- // Remove escape key listener
1037
+ this.isClosing = true;
563
1038
  document.removeEventListener('keydown', this.handleEscapeKey);
564
- // Animate out
565
1039
  this.backdrop.style.opacity = '0';
566
1040
  this.container.style.opacity = '0';
567
- this.container.style.transform = 'translate(-50%, -50%) scale(0.9)';
568
- // Clear any stored callbacks
1041
+ this.container.style.transform = 'translateX(-50%) scale(0.94)';
569
1042
  this.closeCallback = undefined;
570
- // Remove after animation
571
1043
  setTimeout(() => {
572
1044
  var _a, _b;
573
- this.cleanup();
574
- this.isOpen = false;
575
- (_b = (_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.onClose) === null || _b === void 0 ? void 0 : _b.call(_a);
576
- }, 300);
1045
+ if (this.isClosing) {
1046
+ this.cleanup();
1047
+ this.isOpen = false;
1048
+ this.isClosing = false;
1049
+ (_b = (_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.onClose) === null || _b === void 0 ? void 0 : _b.call(_a);
1050
+ }
1051
+ }, 400);
577
1052
  }
578
- /**
579
- * Removes modal elements from DOM
580
- */
581
1053
  cleanup() {
582
1054
  var _a, _b;
583
1055
  (_a = this.container) === null || _a === void 0 ? void 0 : _a.remove();
584
1056
  (_b = this.backdrop) === null || _b === void 0 ? void 0 : _b.remove();
585
1057
  this.container = null;
586
1058
  this.backdrop = null;
587
- }
588
- /**
589
- * Check if modal is currently open
590
- */
591
- isModalOpen() {
592
- return this.isOpen;
1059
+ this.unlockBodyScroll();
593
1060
  }
594
1061
  }