@glideidentity/web-client-sdk 5.1.3 → 6.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/README.md +337 -526
  2. package/dist/browser/web-client-sdk.min.js +1 -1
  3. package/dist/cjs/adapters/index.js +15 -0
  4. package/dist/cjs/adapters/react.js +192 -0
  5. package/dist/cjs/adapters/vanilla.js +38 -0
  6. package/dist/cjs/adapters/vue.js +187 -0
  7. package/dist/cjs/browser.js +58 -0
  8. package/dist/cjs/client/http.js +159 -0
  9. package/dist/cjs/client/index.js +19 -0
  10. package/dist/cjs/client/logger.js +135 -0
  11. package/dist/cjs/client/phone-auth-client.js +439 -0
  12. package/dist/cjs/client/strategies/polling.js +177 -0
  13. package/dist/cjs/core/errors.js +204 -0
  14. package/dist/cjs/core/index.js +83 -0
  15. package/dist/cjs/core/type-guards.js +196 -0
  16. package/dist/cjs/core/types.js +25 -0
  17. package/dist/{core/phone-auth/validation-utils.js → cjs/core/validators.js} +70 -23
  18. package/dist/cjs/index.js +81 -0
  19. package/dist/cjs/ui/index.js +11 -0
  20. package/dist/{core/phone-auth → cjs}/ui/mobile-debug-console.js +149 -78
  21. package/dist/cjs/ui/modal.js +1122 -0
  22. package/dist/esm/adapters/index.js +11 -0
  23. package/dist/esm/adapters/react.js +182 -0
  24. package/dist/esm/adapters/vanilla.js +29 -0
  25. package/dist/esm/adapters/vue.js +177 -0
  26. package/dist/esm/browser.js +30 -11
  27. package/dist/esm/client/http.js +156 -0
  28. package/dist/esm/client/index.js +11 -0
  29. package/dist/esm/client/logger.js +131 -0
  30. package/dist/esm/client/phone-auth-client.js +435 -0
  31. package/dist/esm/client/strategies/polling.js +174 -0
  32. package/dist/esm/core/errors.js +193 -0
  33. package/dist/esm/core/index.js +60 -0
  34. package/dist/esm/core/type-guards.js +181 -0
  35. package/dist/esm/core/types.js +22 -1
  36. package/dist/esm/core/{phone-auth/validation-utils.js → validators.js} +66 -21
  37. package/dist/esm/index.js +45 -17
  38. package/dist/esm/ui/index.js +5 -0
  39. package/dist/esm/{core/phone-auth/ui → ui}/mobile-debug-console.js +149 -78
  40. package/dist/esm/ui/modal.js +1117 -0
  41. package/dist/types/adapters/index.d.ts +10 -0
  42. package/dist/types/adapters/index.d.ts.map +1 -0
  43. package/dist/types/adapters/react.d.ts +70 -0
  44. package/dist/types/adapters/react.d.ts.map +1 -0
  45. package/dist/types/adapters/vanilla.d.ts +29 -0
  46. package/dist/types/adapters/vanilla.d.ts.map +1 -0
  47. package/dist/types/adapters/vue.d.ts +71 -0
  48. package/dist/types/adapters/vue.d.ts.map +1 -0
  49. package/dist/types/browser.d.ts +27 -0
  50. package/dist/types/browser.d.ts.map +1 -0
  51. package/dist/types/client/http.d.ts +41 -0
  52. package/dist/types/client/http.d.ts.map +1 -0
  53. package/dist/types/client/index.d.ts +10 -0
  54. package/dist/types/client/index.d.ts.map +1 -0
  55. package/dist/types/client/logger.d.ts +36 -0
  56. package/dist/types/client/logger.d.ts.map +1 -0
  57. package/dist/types/client/phone-auth-client.d.ts +91 -0
  58. package/dist/types/client/phone-auth-client.d.ts.map +1 -0
  59. package/dist/types/client/strategies/polling.d.ts +36 -0
  60. package/dist/types/client/strategies/polling.d.ts.map +1 -0
  61. package/dist/types/core/errors.d.ts +71 -0
  62. package/dist/types/core/errors.d.ts.map +1 -0
  63. package/dist/types/core/index.d.ts +38 -0
  64. package/dist/types/core/index.d.ts.map +1 -0
  65. package/dist/types/core/type-guards.d.ts +118 -0
  66. package/dist/types/core/type-guards.d.ts.map +1 -0
  67. package/dist/types/core/types.d.ts +534 -0
  68. package/dist/types/core/types.d.ts.map +1 -0
  69. package/dist/types/core/validators.d.ts +63 -0
  70. package/dist/types/core/validators.d.ts.map +1 -0
  71. package/dist/types/index.d.ts +40 -0
  72. package/dist/types/index.d.ts.map +1 -0
  73. package/dist/types/ui/index.d.ts +6 -0
  74. package/dist/types/ui/index.d.ts.map +1 -0
  75. package/dist/{esm/core/phone-auth → types}/ui/mobile-debug-console.d.ts +1 -0
  76. package/dist/types/ui/mobile-debug-console.d.ts.map +1 -0
  77. package/dist/types/ui/modal.d.ts +87 -0
  78. package/dist/types/ui/modal.d.ts.map +1 -0
  79. package/package.json +48 -34
  80. package/dist/adapters/angular/client.service.d.ts +0 -7
  81. package/dist/adapters/angular/client.service.js +0 -30
  82. package/dist/adapters/angular/index.d.ts +0 -3
  83. package/dist/adapters/angular/index.js +0 -18
  84. package/dist/adapters/angular/phone-auth.service.d.ts +0 -38
  85. package/dist/adapters/angular/phone-auth.service.js +0 -130
  86. package/dist/adapters/react/index.d.ts +0 -9
  87. package/dist/adapters/react/index.js +0 -28
  88. package/dist/adapters/react/useClient.d.ts +0 -26
  89. package/dist/adapters/react/useClient.js +0 -121
  90. package/dist/adapters/react/usePhoneAuth.d.ts +0 -23
  91. package/dist/adapters/react/usePhoneAuth.js +0 -95
  92. package/dist/adapters/vanilla/client.d.ts +0 -8
  93. package/dist/adapters/vanilla/client.js +0 -33
  94. package/dist/adapters/vanilla/index.d.ts +0 -3
  95. package/dist/adapters/vanilla/index.js +0 -18
  96. package/dist/adapters/vanilla/phone-auth.d.ts +0 -46
  97. package/dist/adapters/vanilla/phone-auth.js +0 -138
  98. package/dist/adapters/vue/index.d.ts +0 -10
  99. package/dist/adapters/vue/index.js +0 -36
  100. package/dist/adapters/vue/useClient.d.ts +0 -115
  101. package/dist/adapters/vue/useClient.js +0 -131
  102. package/dist/adapters/vue/usePhoneAuth.d.ts +0 -94
  103. package/dist/adapters/vue/usePhoneAuth.js +0 -103
  104. package/dist/browser.d.ts +0 -7
  105. package/dist/browser.js +0 -31
  106. package/dist/core/client.d.ts +0 -22
  107. package/dist/core/client.js +0 -77
  108. package/dist/core/logger.d.ts +0 -130
  109. package/dist/core/logger.js +0 -370
  110. package/dist/core/phone-auth/api-types.d.ts +0 -593
  111. package/dist/core/phone-auth/api-types.js +0 -215
  112. package/dist/core/phone-auth/client.d.ts +0 -189
  113. package/dist/core/phone-auth/client.js +0 -1441
  114. package/dist/core/phone-auth/error-utils.d.ts +0 -110
  115. package/dist/core/phone-auth/error-utils.js +0 -350
  116. package/dist/core/phone-auth/index.d.ts +0 -7
  117. package/dist/core/phone-auth/index.js +0 -50
  118. package/dist/core/phone-auth/status-types.d.ts +0 -107
  119. package/dist/core/phone-auth/status-types.js +0 -31
  120. package/dist/core/phone-auth/strategies/desktop.d.ts +0 -122
  121. package/dist/core/phone-auth/strategies/desktop.js +0 -596
  122. package/dist/core/phone-auth/strategies/index.d.ts +0 -11
  123. package/dist/core/phone-auth/strategies/index.js +0 -15
  124. package/dist/core/phone-auth/strategies/link.d.ts +0 -89
  125. package/dist/core/phone-auth/strategies/link.js +0 -384
  126. package/dist/core/phone-auth/strategies/ts43.d.ts +0 -32
  127. package/dist/core/phone-auth/strategies/ts43.js +0 -161
  128. package/dist/core/phone-auth/strategies/types.d.ts +0 -18
  129. package/dist/core/phone-auth/strategies/types.js +0 -6
  130. package/dist/core/phone-auth/type-guards.d.ts +0 -143
  131. package/dist/core/phone-auth/type-guards.js +0 -198
  132. package/dist/core/phone-auth/types.d.ts +0 -237
  133. package/dist/core/phone-auth/types.js +0 -93
  134. package/dist/core/phone-auth/ui/mobile-debug-console.d.ts +0 -25
  135. package/dist/core/phone-auth/ui/modal.d.ts +0 -88
  136. package/dist/core/phone-auth/ui/modal.js +0 -598
  137. package/dist/core/phone-auth/validation-utils.d.ts +0 -44
  138. package/dist/core/types.d.ts +0 -62
  139. package/dist/core/types.js +0 -2
  140. package/dist/core/version.d.ts +0 -1
  141. package/dist/core/version.js +0 -5
  142. package/dist/esm/adapters/angular/client.service.d.ts +0 -7
  143. package/dist/esm/adapters/angular/client.service.js +0 -27
  144. package/dist/esm/adapters/angular/index.d.ts +0 -3
  145. package/dist/esm/adapters/angular/index.js +0 -4
  146. package/dist/esm/adapters/angular/phone-auth.service.d.ts +0 -38
  147. package/dist/esm/adapters/angular/phone-auth.service.js +0 -127
  148. package/dist/esm/adapters/react/index.d.ts +0 -9
  149. package/dist/esm/adapters/react/index.js +0 -8
  150. package/dist/esm/adapters/react/useClient.d.ts +0 -26
  151. package/dist/esm/adapters/react/useClient.js +0 -116
  152. package/dist/esm/adapters/react/usePhoneAuth.d.ts +0 -23
  153. package/dist/esm/adapters/react/usePhoneAuth.js +0 -92
  154. package/dist/esm/adapters/vanilla/client.d.ts +0 -8
  155. package/dist/esm/adapters/vanilla/client.js +0 -29
  156. package/dist/esm/adapters/vanilla/index.d.ts +0 -3
  157. package/dist/esm/adapters/vanilla/index.js +0 -4
  158. package/dist/esm/adapters/vanilla/phone-auth.d.ts +0 -46
  159. package/dist/esm/adapters/vanilla/phone-auth.js +0 -134
  160. package/dist/esm/adapters/vue/index.d.ts +0 -10
  161. package/dist/esm/adapters/vue/index.js +0 -11
  162. package/dist/esm/adapters/vue/useClient.d.ts +0 -115
  163. package/dist/esm/adapters/vue/useClient.js +0 -127
  164. package/dist/esm/adapters/vue/usePhoneAuth.d.ts +0 -94
  165. package/dist/esm/adapters/vue/usePhoneAuth.js +0 -100
  166. package/dist/esm/browser.d.ts +0 -7
  167. package/dist/esm/core/client.d.ts +0 -22
  168. package/dist/esm/core/client.js +0 -70
  169. package/dist/esm/core/logger.d.ts +0 -130
  170. package/dist/esm/core/logger.js +0 -359
  171. package/dist/esm/core/phone-auth/api-types.d.ts +0 -593
  172. package/dist/esm/core/phone-auth/api-types.js +0 -203
  173. package/dist/esm/core/phone-auth/client.d.ts +0 -189
  174. package/dist/esm/core/phone-auth/client.js +0 -1404
  175. package/dist/esm/core/phone-auth/error-utils.d.ts +0 -110
  176. package/dist/esm/core/phone-auth/error-utils.js +0 -338
  177. package/dist/esm/core/phone-auth/index.d.ts +0 -7
  178. package/dist/esm/core/phone-auth/index.js +0 -8
  179. package/dist/esm/core/phone-auth/status-types.d.ts +0 -107
  180. package/dist/esm/core/phone-auth/status-types.js +0 -26
  181. package/dist/esm/core/phone-auth/strategies/desktop.d.ts +0 -122
  182. package/dist/esm/core/phone-auth/strategies/desktop.js +0 -590
  183. package/dist/esm/core/phone-auth/strategies/index.d.ts +0 -11
  184. package/dist/esm/core/phone-auth/strategies/index.js +0 -7
  185. package/dist/esm/core/phone-auth/strategies/link.d.ts +0 -89
  186. package/dist/esm/core/phone-auth/strategies/link.js +0 -380
  187. package/dist/esm/core/phone-auth/strategies/ts43.d.ts +0 -32
  188. package/dist/esm/core/phone-auth/strategies/ts43.js +0 -157
  189. package/dist/esm/core/phone-auth/strategies/types.d.ts +0 -18
  190. package/dist/esm/core/phone-auth/strategies/types.js +0 -5
  191. package/dist/esm/core/phone-auth/type-guards.d.ts +0 -143
  192. package/dist/esm/core/phone-auth/type-guards.js +0 -185
  193. package/dist/esm/core/phone-auth/types.d.ts +0 -237
  194. package/dist/esm/core/phone-auth/types.js +0 -76
  195. package/dist/esm/core/phone-auth/ui/modal.d.ts +0 -88
  196. package/dist/esm/core/phone-auth/ui/modal.js +0 -594
  197. package/dist/esm/core/phone-auth/validation-utils.d.ts +0 -44
  198. package/dist/esm/core/types.d.ts +0 -62
  199. package/dist/esm/core/version.d.ts +0 -1
  200. package/dist/esm/core/version.js +0 -2
  201. package/dist/esm/index.d.ts +0 -12
  202. package/dist/index.d.ts +0 -12
  203. package/dist/index.js +0 -55
@@ -0,0 +1,1122 @@
1
+ "use strict";
2
+ /**
3
+ * Modal UI Component for Phone Authentication
4
+ *
5
+ * This file creates the UI components (modals, buttons) that are shown
6
+ * for Desktop QR code authentication flow.
7
+ *
8
+ * Supports three view modes:
9
+ * - toggle: Single QR with iOS/Android toggle (default)
10
+ * - dual: Side-by-side QR codes for both platforms
11
+ * - pre-step: OS selection first, then QR
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.AuthModal = void 0;
15
+ exports.createQRCodeDataFromDesktop = createQRCodeDataFromDesktop;
16
+ /**
17
+ * Creates and manages a modal dialog for authentication
18
+ */
19
+ class AuthModal {
20
+ constructor(options) {
21
+ this.container = null;
22
+ this.backdrop = null;
23
+ this.isOpen = false;
24
+ // State for Pre-step mode
25
+ this.currentStep = 'os-choice';
26
+ this.qrCodeData = null;
27
+ this.statusMessage = '';
28
+ // Body scroll lock state
29
+ this.originalBodyOverflow = '';
30
+ // Close animation state
31
+ this.isClosing = false;
32
+ // Icons
33
+ 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>`;
34
+ 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>`;
35
+ 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>`;
36
+ this.options = options || {};
37
+ this.theme = this.options.theme || 'auto';
38
+ this.handleEscapeKey = this.handleEscapeKey.bind(this);
39
+ }
40
+ /**
41
+ * Determines if dark mode should be used based on theme setting
42
+ */
43
+ shouldUseDarkMode() {
44
+ if (this.theme === 'dark')
45
+ return true;
46
+ if (this.theme === 'light')
47
+ return false;
48
+ // Auto - use system preference
49
+ return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
50
+ }
51
+ handleEscapeKey(event) {
52
+ if (event.key === 'Escape' && this.isOpen) {
53
+ if (this.options?.closeOnEscape !== false) {
54
+ this.closeCallback?.();
55
+ this.close();
56
+ }
57
+ }
58
+ }
59
+ escapeHtml(unsafe) {
60
+ return unsafe
61
+ .replace(/&/g, "&amp;")
62
+ .replace(/</g, "&lt;")
63
+ .replace(/>/g, "&gt;")
64
+ .replace(/"/g, "&quot;")
65
+ .replace(/'/g, "&#039;");
66
+ }
67
+ /**
68
+ * Shows the modal with a QR code
69
+ * @param qrCodeData - Either a string (single QR) or QRCodeData (separate iOS/Android)
70
+ * @param statusMessage - Optional status message to display
71
+ */
72
+ showQRCode(qrCodeData, statusMessage = 'Scan with your phone camera') {
73
+ this.qrCodeData = qrCodeData;
74
+ this.statusMessage = statusMessage;
75
+ // Determine mode
76
+ const mode = this.options.viewMode || 'toggle';
77
+ // If legacy string data, force toggle mode
78
+ if (typeof qrCodeData === 'string') {
79
+ this.renderToggleMode(qrCodeData, statusMessage);
80
+ }
81
+ else {
82
+ switch (mode) {
83
+ case 'dual':
84
+ this.renderDualMode(qrCodeData, statusMessage);
85
+ break;
86
+ case 'pre-step':
87
+ this.renderPreStepMode(qrCodeData, statusMessage);
88
+ break;
89
+ case 'toggle':
90
+ default:
91
+ this.renderToggleMode(qrCodeData, statusMessage);
92
+ break;
93
+ }
94
+ }
95
+ this.show();
96
+ }
97
+ /**
98
+ * Updates the status message shown in the modal
99
+ */
100
+ updateStatus(status, isError = false) {
101
+ this.statusMessage = status;
102
+ if (this.container) {
103
+ const el = this.container.querySelector('#glide-status');
104
+ if (el) {
105
+ el.textContent = status;
106
+ if (isError) {
107
+ el.style.color = '#ff3b30';
108
+ }
109
+ else {
110
+ el.style.color = '';
111
+ }
112
+ }
113
+ }
114
+ }
115
+ setCloseCallback(callback) {
116
+ this.closeCallback = callback;
117
+ }
118
+ // --- Renderers ---
119
+ renderToggleMode(data, msg) {
120
+ let iosQR = '';
121
+ let androidQR = '';
122
+ if (typeof data === 'string') {
123
+ iosQR = data;
124
+ androidQR = data;
125
+ }
126
+ else {
127
+ iosQR = data.iosQRCode;
128
+ androidQR = data.androidQRCode || data.iosQRCode;
129
+ }
130
+ const content = `
131
+ <div class="glide-content">
132
+ <div class="glide-toggle" id="glide-toggle" data-active="ios">
133
+ <div class="glide-toggle-slider"></div>
134
+ <button class="glide-btn glide-toggle-btn active" data-platform="ios">${this.iconApple}<span>iOS</span></button>
135
+ <button class="glide-btn glide-toggle-btn" data-platform="android">${this.iconAndroid}<span>Android</span></button>
136
+ </div>
137
+
138
+ <div class="glide-qr-area">
139
+ <img
140
+ class="glide-qr-img"
141
+ id="glide-qr-img"
142
+ src="${this.escapeHtml(iosQR)}"
143
+ alt="QR Code"
144
+ class="glide-qr-img"
145
+ data-ios="${this.escapeHtml(iosQR)}"
146
+ data-android="${this.escapeHtml(androidQR)}"
147
+ />
148
+
149
+ <!-- Animation Overlay (Inside QR Area for centering) -->
150
+ <div id="glide-phone-overlay" class="glide-phone-animation-overlay">
151
+ <div class="glide-outlined-phone">
152
+ <div class="glide-scan-line"></div>
153
+ </div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ `;
158
+ this.createModal(content, '', true);
159
+ this.setupPlatformToggles();
160
+ this.setupHelpInteraction();
161
+ }
162
+ renderDualMode(data, msg) {
163
+ const qrOverlayHtml = `
164
+ <div class="glide-phone-animation-overlay">
165
+ <div class="glide-outlined-phone">
166
+ <div class="glide-scan-line"></div>
167
+ </div>
168
+ </div>
169
+ `;
170
+ this.createModal(`
171
+ <div class="glide-content glide-dual-mode">
172
+ <div class="glide-dual-container">
173
+ <div class="glide-dual-item">
174
+ <div class="glide-os-logo">
175
+ ${this.iconApple}
176
+ <span>iOS</span>
177
+ </div>
178
+ <div class="glide-qr-area">
179
+ <img src="${this.escapeHtml(data.iosQRCode)}" alt="iOS QR" class="glide-qr-img" />
180
+ ${qrOverlayHtml}
181
+ </div>
182
+ </div>
183
+ <div class="glide-dual-separator">
184
+ <div class="glide-separator-line"></div>
185
+ <span class="glide-separator-text">or</span>
186
+ <div class="glide-separator-line"></div>
187
+ </div>
188
+ <div class="glide-dual-item">
189
+ <div class="glide-os-logo">
190
+ ${this.iconAndroid}
191
+ <span>Android</span>
192
+ </div>
193
+ <div class="glide-qr-area">
194
+ <img src="${this.escapeHtml(data.androidQRCode || data.iosQRCode)}" alt="Android QR" class="glide-qr-img" />
195
+ ${qrOverlayHtml}
196
+ </div>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ `, 'glide-modal-wide', true);
201
+ this.setupHelpInteraction();
202
+ }
203
+ renderPreStepMode(data, msg) {
204
+ this.currentStep = 'os-choice';
205
+ // First create the initial modal with OS choice buttons
206
+ const contentHtml = `
207
+ <div class="glide-pre-step-container">
208
+ <button class="glide-os-choice-btn" id="glide-btn-ios">
209
+ ${this.iconApple}
210
+ <span>iOS</span>
211
+ </button>
212
+ <button class="glide-os-choice-btn" id="glide-btn-android">
213
+ ${this.iconAndroid}
214
+ <span>Android</span>
215
+ </button>
216
+ </div>
217
+ `;
218
+ this.createModal(contentHtml, '', false);
219
+ this.setupPreStepListeners();
220
+ this.show();
221
+ }
222
+ setupPreStepListeners() {
223
+ if (!this.container)
224
+ return;
225
+ this.container.querySelector('#glide-btn-ios')?.addEventListener('click', () => {
226
+ this.currentStep = 'ios-qr';
227
+ this.updatePreStepUI();
228
+ });
229
+ this.container.querySelector('#glide-btn-android')?.addEventListener('click', () => {
230
+ this.currentStep = 'android-qr';
231
+ this.updatePreStepUI();
232
+ });
233
+ }
234
+ updatePreStepUI() {
235
+ if (!this.container)
236
+ return;
237
+ let contentHtml = '';
238
+ if (this.currentStep === 'os-choice') {
239
+ // Back to OS choice
240
+ contentHtml = `
241
+ <div class="glide-pre-step-container">
242
+ <button class="glide-os-choice-btn" id="glide-btn-ios">
243
+ ${this.iconApple}
244
+ <span>iOS</span>
245
+ </button>
246
+ <button class="glide-os-choice-btn" id="glide-btn-android">
247
+ ${this.iconAndroid}
248
+ <span>Android</span>
249
+ </button>
250
+ </div>
251
+ `;
252
+ this.createModal(contentHtml, '', false);
253
+ this.setupPreStepListeners();
254
+ }
255
+ else if (this.currentStep === 'ios-qr') {
256
+ const qr = (typeof this.qrCodeData === 'object' && this.qrCodeData?.iosQRCode)
257
+ ? this.qrCodeData.iosQRCode
258
+ : this.qrCodeData;
259
+ contentHtml = `
260
+ <div class="glide-content">
261
+ <div class="glide-qr-area">
262
+ <img src="${this.escapeHtml(qr)}" alt="QR Code" class="glide-qr-img" />
263
+
264
+ <!-- Animation Overlay -->
265
+ <div id="glide-phone-overlay" class="glide-phone-animation-overlay">
266
+ <div class="glide-outlined-phone">
267
+ <div class="glide-scan-line"></div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ `;
273
+ // includeHelpButton = true, includeBackButton = true
274
+ this.createModal(contentHtml, '', true, true);
275
+ this.setupBackButton();
276
+ this.setupHelpInteraction();
277
+ }
278
+ else if (this.currentStep === 'android-qr') {
279
+ const qr = (typeof this.qrCodeData === 'object' && this.qrCodeData?.androidQRCode)
280
+ ? this.qrCodeData.androidQRCode
281
+ : (typeof this.qrCodeData === 'object' && this.qrCodeData?.iosQRCode)
282
+ ? this.qrCodeData.iosQRCode // Fallback to iOS QR if no Android QR
283
+ : this.qrCodeData;
284
+ contentHtml = `
285
+ <div class="glide-content">
286
+ <div class="glide-qr-area">
287
+ <img src="${this.escapeHtml(qr)}" alt="QR Code" class="glide-qr-img" />
288
+
289
+ <!-- Animation Overlay -->
290
+ <div id="glide-phone-overlay" class="glide-phone-animation-overlay">
291
+ <div class="glide-outlined-phone">
292
+ <div class="glide-scan-line"></div>
293
+ </div>
294
+ </div>
295
+ </div>
296
+ </div>
297
+ `;
298
+ // includeHelpButton = true, includeBackButton = true
299
+ this.createModal(contentHtml, '', true, true);
300
+ this.setupBackButton();
301
+ this.setupHelpInteraction();
302
+ }
303
+ }
304
+ setupBackButton() {
305
+ if (!this.container)
306
+ return;
307
+ this.container.querySelector('#glide-back-btn')?.addEventListener('click', () => {
308
+ this.currentStep = 'os-choice';
309
+ this.updatePreStepUI();
310
+ });
311
+ }
312
+ // --- Core Methods ---
313
+ createModal(content, extraClass = '', includeHelpButton = false, includeBackButton = false) {
314
+ // If a modal is closing, force cleanup before creating new one
315
+ if (this.isClosing) {
316
+ this.isClosing = false;
317
+ this.cleanup();
318
+ }
319
+ if (!this.container) {
320
+ this.backdrop = document.createElement('div');
321
+ this.backdrop.className = 'glide-backdrop';
322
+ this.backdrop.id = 'glide-backdrop';
323
+ this.container = document.createElement('div');
324
+ this.container.className = `glide-modal ${extraClass}`;
325
+ this.container.id = 'glide-modal';
326
+ if (this.shouldUseDarkMode()) {
327
+ this.container.classList.add('dark');
328
+ }
329
+ if (this.isOpen) {
330
+ document.body.appendChild(this.backdrop);
331
+ document.body.appendChild(this.container);
332
+ }
333
+ }
334
+ else {
335
+ this.container.className = `glide-modal ${extraClass}`;
336
+ if (this.shouldUseDarkMode()) {
337
+ this.container.classList.add('dark');
338
+ }
339
+ }
340
+ this.container.innerHTML = `
341
+ ${includeBackButton ? `
342
+ <button class="glide-btn glide-btn-back" id="glide-back-btn" aria-label="Back">
343
+ ${this.iconBack}
344
+ </button>
345
+ ` : ''}
346
+ ${this.options?.showCloseButton !== false ? `
347
+ <button class="glide-btn glide-btn-close" id="glide-close-btn" aria-label="Close">
348
+ <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">
349
+ <line x1="18" y1="6" x2="6" y2="18"></line>
350
+ <line x1="6" y1="6" x2="18" y2="18"></line>
351
+ </svg>
352
+ </button>
353
+ ` : ''}
354
+ <h2 class="glide-title">${this.options?.title || 'Scan to Verify'}</h2>
355
+ <div id="glide-modal-content">${content}</div>
356
+ <p class="glide-status" id="glide-status">${this.options?.description || ''}</p>
357
+ ${includeHelpButton ? `
358
+ <button class="glide-btn glide-btn-help" id="glide-help-btn">?</button>
359
+ ` : ''}
360
+ `;
361
+ this.injectStyles();
362
+ const closeBtn = this.container.querySelector('#glide-close-btn');
363
+ if (closeBtn) {
364
+ closeBtn.addEventListener('click', () => {
365
+ this.closeCallback?.();
366
+ this.close();
367
+ });
368
+ }
369
+ if (this.backdrop) {
370
+ this.backdrop.onclick = (e) => {
371
+ if (e.target === this.backdrop && this.options?.closeOnBackdropClick !== false) {
372
+ this.closeCallback?.();
373
+ this.close();
374
+ }
375
+ };
376
+ }
377
+ }
378
+ setupHelpInteraction() {
379
+ const trigger = this.container?.querySelector('#glide-help-btn');
380
+ if (trigger) {
381
+ trigger.addEventListener('click', () => {
382
+ // Select all overlays (handles single and dual mode)
383
+ const overlays = this.container?.querySelectorAll('.glide-phone-animation-overlay');
384
+ if (overlays) {
385
+ overlays.forEach(overlay => {
386
+ overlay.classList.remove('playing');
387
+ void overlay.offsetWidth; // Trigger reflow
388
+ overlay.classList.add('playing');
389
+ // Auto-hide after animation (3s)
390
+ setTimeout(() => {
391
+ overlay.classList.remove('playing');
392
+ }, 3000);
393
+ });
394
+ }
395
+ });
396
+ }
397
+ }
398
+ injectStyles() {
399
+ if (document.getElementById('glide-modal-styles'))
400
+ return;
401
+ const styles = document.createElement('style');
402
+ styles.id = 'glide-modal-styles';
403
+ styles.textContent = `
404
+ :root {
405
+ --glide-primary: #007AFF;
406
+ --glide-text: #1d1d1f;
407
+ --glide-bg-light: rgba(255, 255, 255, 0.6);
408
+ --glide-bg-dark: rgba(30, 30, 30, 0.6);
409
+ --glide-phone-border: #000000; /* High Contrast Black */
410
+
411
+ /* Button sizes */
412
+ --glide-btn-size: 28px;
413
+ --glide-help-btn-size: 24px;
414
+ --glide-toggle-icon-size: 14px;
415
+ --glide-dual-icon-size: 18px;
416
+ }
417
+
418
+ #glide-backdrop {
419
+ position: fixed;
420
+ top: 0;
421
+ left: 0;
422
+ right: 0;
423
+ bottom: 0;
424
+ background: rgba(0, 0, 0, 0.2);
425
+ backdrop-filter: blur(8px);
426
+ -webkit-backdrop-filter: blur(8px);
427
+ z-index: 9998;
428
+ opacity: 0;
429
+ transition: opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1);
430
+ }
431
+
432
+ #glide-modal {
433
+ position: fixed;
434
+ top: 20px;
435
+ left: 50%;
436
+ transform: translateX(-50%) scale(0.94);
437
+ background: var(--glide-bg-light);
438
+ backdrop-filter: blur(24px) saturate(180%);
439
+ -webkit-backdrop-filter: blur(24px) saturate(180%);
440
+ border-radius: 24px;
441
+ padding: 32px;
442
+ width: 360px;
443
+ max-width: 90%;
444
+ box-shadow:
445
+ 0 20px 40px rgba(0,0,0,0.2),
446
+ 0 0 0 1px rgba(255,255,255,0.6) inset,
447
+ 0 0 0 1px rgba(0,0,0,0.05);
448
+ z-index: 9999;
449
+ opacity: 0;
450
+ transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
451
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
452
+ text-align: center;
453
+ color: var(--glide-text);
454
+ }
455
+
456
+ #glide-modal.glide-modal-wide {
457
+ width: 600px;
458
+ max-width: 95vw;
459
+ }
460
+
461
+ #glide-modal.dark {
462
+ background: var(--glide-bg-dark);
463
+ color: white;
464
+ box-shadow:
465
+ 0 20px 40px rgba(0,0,0,0.4),
466
+ 0 0 0 1px rgba(255,255,255,0.15) inset,
467
+ 0 0 0 1px rgba(0,0,0,0.5);
468
+ --glide-phone-border: #ffffff; /* High Contrast White */
469
+ }
470
+
471
+ #glide-modal .glide-btn-close {
472
+ position: absolute;
473
+ top: 16px;
474
+ right: 16px;
475
+ width: var(--glide-btn-size);
476
+ height: var(--glide-btn-size);
477
+ min-width: var(--glide-btn-size);
478
+ min-height: var(--glide-btn-size);
479
+ max-width: var(--glide-btn-size);
480
+ max-height: var(--glide-btn-size);
481
+ background: rgba(118, 118, 128, 0.12);
482
+ border: none;
483
+ border-radius: 50%;
484
+ display: flex;
485
+ align-items: center;
486
+ justify-content: center;
487
+ cursor: pointer;
488
+ color: rgba(0,0,0,0.5);
489
+ transition: background 0.2s;
490
+ padding: 0;
491
+ z-index: 20;
492
+ box-sizing: border-box;
493
+ flex-shrink: 0;
494
+ }
495
+
496
+ #glide-modal.dark .glide-btn-close {
497
+ background: rgba(255, 255, 255, 0.1);
498
+ color: rgba(255,255,255,0.5);
499
+ }
500
+
501
+ #glide-modal .glide-btn-close:hover {
502
+ background: rgba(118, 118, 128, 0.2);
503
+ }
504
+
505
+ #glide-modal.dark .glide-btn-close:hover {
506
+ background: rgba(255, 255, 255, 0.2);
507
+ }
508
+
509
+ .glide-title {
510
+ margin: 0 0 8px 0;
511
+ font-size: 22px;
512
+ font-weight: 600;
513
+ letter-spacing: -0.01em;
514
+ }
515
+
516
+ .glide-status {
517
+ margin: 12px 0 0 0;
518
+ font-size: 13px;
519
+ color: rgba(0,0,0,0.5);
520
+ text-align: center;
521
+ min-height: 18px;
522
+ }
523
+
524
+ .glide-status:empty {
525
+ display: none;
526
+ }
527
+
528
+ #glide-modal.dark .glide-status {
529
+ color: rgba(255,255,255,0.5);
530
+ }
531
+
532
+ .glide-content {
533
+ display: flex;
534
+ flex-direction: column;
535
+ align-items: center;
536
+ position: relative;
537
+ }
538
+
539
+ /* --- Sliding Toggle Switch --- */
540
+ #glide-toggle {
541
+ background: rgba(118, 118, 128, 0.12);
542
+ padding: 2px;
543
+ border-radius: 8px;
544
+ display: inline-flex;
545
+ margin-bottom: 24px;
546
+ margin-top: 16px;
547
+ position: relative;
548
+ height: 32px;
549
+ width: 200px;
550
+ box-sizing: border-box;
551
+ }
552
+
553
+ #glide-modal.dark #glide-toggle {
554
+ background: rgba(118, 118, 128, 0.24);
555
+ }
556
+
557
+ /* The sliding background */
558
+ .glide-toggle-slider {
559
+ position: absolute;
560
+ top: 2px;
561
+ left: 2px;
562
+ width: calc(50% - 2px);
563
+ height: calc(100% - 4px);
564
+ background: white;
565
+ border-radius: 6px;
566
+ box-shadow: 0 3px 8px rgba(0,0,0,0.12), 0 3px 1px rgba(0,0,0,0.04);
567
+ transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
568
+ z-index: 0;
569
+ }
570
+
571
+ #glide-modal.dark .glide-toggle-slider {
572
+ background: #636366;
573
+ }
574
+
575
+ /* Move slider for Android */
576
+ #glide-toggle[data-active="android"] .glide-toggle-slider {
577
+ transform: translateX(100%);
578
+ }
579
+
580
+ .glide-toggle-btn {
581
+ flex: 1;
582
+ background: none;
583
+ border: none;
584
+ padding: 0;
585
+ margin: 0;
586
+ font-size: 13px;
587
+ font-weight: 500;
588
+ color: inherit;
589
+ cursor: pointer;
590
+ position: relative;
591
+ z-index: 1;
592
+ transition: color 0.2s;
593
+ display: flex;
594
+ align-items: center;
595
+ justify-content: center;
596
+ gap: 6px;
597
+ min-height: auto;
598
+ height: auto;
599
+ min-width: auto;
600
+ border-radius: 0;
601
+ }
602
+
603
+ #glide-modal .glide-toggle-btn .glide-icon-os,
604
+ #glide-modal .glide-toggle-btn svg {
605
+ width: var(--glide-toggle-icon-size);
606
+ height: var(--glide-toggle-icon-size);
607
+ flex-shrink: 0;
608
+ margin: 0;
609
+ padding: 0;
610
+ }
611
+
612
+ .glide-toggle-btn span {
613
+ margin: 0;
614
+ padding: 0;
615
+ line-height: 1;
616
+ }
617
+
618
+ #glide-modal.dark .glide-toggle-btn {
619
+ color: rgba(255,255,255,0.6);
620
+ }
621
+
622
+ #glide-toggle[data-active="ios"] .glide-toggle-btn[data-platform="ios"],
623
+ #glide-toggle[data-active="android"] .glide-toggle-btn[data-platform="android"] {
624
+ color: #1d1d1f;
625
+ }
626
+
627
+ #glide-modal.dark #glide-toggle[data-active="ios"] .glide-toggle-btn[data-platform="ios"],
628
+ #glide-modal.dark #glide-toggle[data-active="android"] .glide-toggle-btn[data-platform="android"] {
629
+ color: white;
630
+ }
631
+
632
+ /* QR Area */
633
+ .glide-qr-area {
634
+ position: relative;
635
+ margin: 0 auto;
636
+ }
637
+
638
+ .glide-qr-img {
639
+ width: 200px;
640
+ height: 200px;
641
+ object-fit: contain;
642
+ display: block;
643
+ border-radius: 16px;
644
+ }
645
+
646
+ /* Dual Mode QR Area - no extra padding, same as single mode */
647
+ .glide-dual-mode .glide-qr-area {
648
+ background: transparent;
649
+ padding: 0;
650
+ }
651
+
652
+ .glide-dual-mode .glide-qr-img {
653
+ width: 180px;
654
+ height: 180px;
655
+ border-radius: 16px;
656
+ }
657
+
658
+ /* --- Help Icon & Interaction --- */
659
+ .glide-btn-help {
660
+ position: absolute;
661
+ bottom: 20px;
662
+ right: 20px;
663
+ width: var(--glide-help-btn-size);
664
+ height: var(--glide-help-btn-size);
665
+ min-width: var(--glide-help-btn-size);
666
+ min-height: var(--glide-help-btn-size);
667
+ max-width: var(--glide-help-btn-size);
668
+ max-height: var(--glide-help-btn-size);
669
+ border-radius: 50%;
670
+ border: 1.5px solid rgba(0,0,0,0.2);
671
+ color: rgba(0,0,0,0.4);
672
+ display: flex;
673
+ align-items: center;
674
+ justify-content: center;
675
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
676
+ font-size: 14px;
677
+ font-weight: 600;
678
+ line-height: 1;
679
+ cursor: pointer;
680
+ transition: all 0.2s;
681
+ background: transparent;
682
+ padding: 0;
683
+ margin: 0;
684
+ box-sizing: border-box;
685
+ z-index: 10;
686
+ }
687
+
688
+ #glide-modal.dark .glide-btn-help {
689
+ border-color: rgba(255,255,255,0.3);
690
+ color: rgba(255,255,255,0.5);
691
+ }
692
+
693
+ .glide-btn-help:hover {
694
+ border-color: var(--glide-primary);
695
+ color: var(--glide-primary);
696
+ background: rgba(0, 122, 255, 0.1);
697
+ }
698
+
699
+ /* Tooltip */
700
+ .glide-btn-help::after {
701
+ content: "Need help?";
702
+ position: absolute;
703
+ bottom: 100%;
704
+ left: 50%;
705
+ transform: translateX(-50%) translateY(-8px);
706
+ background: rgba(0,0,0,0.8);
707
+ color: white;
708
+ padding: 4px 8px;
709
+ border-radius: 4px;
710
+ font-size: 11px;
711
+ white-space: nowrap;
712
+ opacity: 0;
713
+ pointer-events: none;
714
+ transition: opacity 0.2s;
715
+ }
716
+
717
+ .glide-btn-help:hover::after {
718
+ opacity: 1;
719
+ }
720
+
721
+ /* --- Outlined Phone Animation --- */
722
+ .glide-phone-animation-overlay {
723
+ position: absolute;
724
+ top: 0;
725
+ left: 0;
726
+ width: 100%;
727
+ height: 100%;
728
+ display: flex;
729
+ align-items: center;
730
+ justify-content: center;
731
+ pointer-events: none;
732
+ opacity: 0;
733
+ transition: opacity 0.3s;
734
+ border-radius: 16px;
735
+ background: rgba(255,255,255,0.8);
736
+ }
737
+
738
+ #glide-modal.dark .glide-phone-animation-overlay {
739
+ background: rgba(0,0,0,0.6);
740
+ }
741
+
742
+ .glide-phone-animation-overlay.playing {
743
+ opacity: 1;
744
+ }
745
+
746
+ .glide-outlined-phone {
747
+ width: 100px;
748
+ height: 180px;
749
+ border: 4px solid var(--glide-phone-border);
750
+ border-radius: 16px;
751
+ position: relative;
752
+ background: transparent;
753
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
754
+ transform: translateY(20px);
755
+ }
756
+
757
+ /* Notch */
758
+ .glide-outlined-phone::before {
759
+ content: '';
760
+ position: absolute;
761
+ top: -1px;
762
+ left: 50%;
763
+ transform: translateX(-50%);
764
+ width: 40%;
765
+ height: 12px;
766
+ background: var(--glide-phone-border);
767
+ border-bottom-left-radius: 8px;
768
+ border-bottom-right-radius: 8px;
769
+ }
770
+
771
+ /* Scanning Line */
772
+ .glide-scan-line {
773
+ position: absolute;
774
+ top: 10%;
775
+ left: 5%;
776
+ width: 90%;
777
+ height: 2px;
778
+ background: var(--glide-primary);
779
+ box-shadow: 0 0 8px var(--glide-primary);
780
+ opacity: 0;
781
+ }
782
+
783
+ /* Animation Keyframes */
784
+ @keyframes glide-scan-motion {
785
+ 0% { top: 10%; opacity: 0; }
786
+ 10% { opacity: 1; }
787
+ 90% { opacity: 1; }
788
+ 100% { top: 90%; opacity: 0; }
789
+ }
790
+
791
+ .glide-phone-animation-overlay.playing .glide-outlined-phone {
792
+ animation: glide-phone-appear 3s ease-in-out forwards;
793
+ }
794
+
795
+ .glide-phone-animation-overlay.playing .glide-scan-line {
796
+ animation: glide-scan-motion 2s ease-in-out 0.5s infinite;
797
+ }
798
+
799
+ @keyframes glide-phone-appear {
800
+ 0% { transform: translateY(20px); opacity: 0; }
801
+ 10% { transform: translateY(0); opacity: 1; }
802
+ 90% { transform: translateY(0); opacity: 1; }
803
+ 100% { transform: translateY(20px); opacity: 0; }
804
+ }
805
+
806
+ /* Dual Mode */
807
+ .glide-dual-container {
808
+ display: flex;
809
+ justify-content: center;
810
+ align-items: stretch;
811
+ gap: 32px;
812
+ margin-top: 20px;
813
+ }
814
+
815
+ .glide-dual-item {
816
+ display: flex;
817
+ flex-direction: column;
818
+ align-items: center;
819
+ }
820
+
821
+ .glide-os-logo {
822
+ display: flex;
823
+ align-items: center;
824
+ gap: 8px;
825
+ font-weight: 600;
826
+ margin-bottom: 12px;
827
+ font-size: 15px;
828
+ color: inherit;
829
+ }
830
+
831
+ /* Dual mode OS logo - aligned icons with text, spacing to QR */
832
+ .glide-dual-mode .glide-os-logo {
833
+ margin-bottom: 12px; /* Space between label and QR code */
834
+ }
835
+
836
+ #glide-modal .glide-dual-mode .glide-os-logo .glide-icon-os,
837
+ #glide-modal .glide-dual-mode .glide-os-logo svg {
838
+ width: var(--glide-dual-icon-size);
839
+ height: var(--glide-dual-icon-size);
840
+ margin: 0;
841
+ padding: 0;
842
+ flex-shrink: 0;
843
+ }
844
+
845
+ .glide-dual-mode .glide-os-logo span {
846
+ margin: 0;
847
+ padding: 0;
848
+ line-height: 1;
849
+ }
850
+
851
+ .glide-os-logo svg {
852
+ width: 20px;
853
+ height: 20px;
854
+ opacity: 0.8;
855
+ }
856
+
857
+ /* Dual Mode Separator - aligned to QR codes only */
858
+ .glide-dual-separator {
859
+ display: flex;
860
+ flex-direction: column;
861
+ align-items: center;
862
+ justify-content: center;
863
+ align-self: stretch;
864
+ /* Offset for OS label (18px icon + 12px margin = ~30px) */
865
+ margin-top: 30px;
866
+ padding: 20px 0;
867
+ }
868
+
869
+ .glide-separator-line {
870
+ width: 1px;
871
+ flex: 1;
872
+ background: linear-gradient(to bottom, transparent, rgba(128,128,128,0.3), transparent);
873
+ }
874
+
875
+ .glide-separator-text {
876
+ padding: 8px 0;
877
+ font-size: 11px;
878
+ font-weight: 500;
879
+ color: rgba(128,128,128,0.5);
880
+ text-transform: uppercase;
881
+ letter-spacing: 0.5px;
882
+ }
883
+
884
+ #glide-modal.dark .glide-separator-line {
885
+ background: linear-gradient(to bottom, transparent, rgba(255,255,255,0.2), transparent);
886
+ }
887
+
888
+ #glide-modal.dark .glide-separator-text {
889
+ color: rgba(255,255,255,0.4);
890
+ }
891
+
892
+ /* Pre-Step */
893
+ .glide-pre-step-container {
894
+ display: flex;
895
+ gap: 20px;
896
+ justify-content: center;
897
+ margin: 24px 0;
898
+ }
899
+
900
+ .glide-os-choice-btn {
901
+ display: flex;
902
+ flex-direction: column;
903
+ align-items: center;
904
+ justify-content: center;
905
+ width: 140px;
906
+ height: 140px;
907
+ border: 1px solid rgba(0,0,0,0.08);
908
+ border-radius: 20px;
909
+ background: rgba(255,255,255,0.4);
910
+ cursor: pointer;
911
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
912
+ position: relative;
913
+ overflow: hidden;
914
+ font-size: 14px;
915
+ color: inherit;
916
+ }
917
+
918
+ #glide-modal.dark .glide-os-choice-btn {
919
+ background: rgba(255,255,255,0.05);
920
+ border-color: rgba(255,255,255,0.1);
921
+ color: white;
922
+ }
923
+
924
+ .glide-os-choice-btn:hover {
925
+ background: rgba(255,255,255,0.9);
926
+ transform: translateY(-4px);
927
+ box-shadow: 0 12px 24px rgba(0,0,0,0.1), 0 4px 8px rgba(0,0,0,0.04);
928
+ border-color: var(--glide-primary);
929
+ }
930
+
931
+ #glide-modal.dark .glide-os-choice-btn:hover {
932
+ background: rgba(255,255,255,0.15);
933
+ box-shadow: 0 12px 24px rgba(0,0,0,0.3);
934
+ border-color: var(--glide-primary);
935
+ }
936
+
937
+ .glide-icon-os {
938
+ width: 40px;
939
+ height: 40px;
940
+ margin-bottom: 12px;
941
+ transition: transform 0.3s;
942
+ fill: currentColor;
943
+ }
944
+
945
+ .glide-os-choice-btn:hover .glide-icon-os {
946
+ transform: scale(1.1);
947
+ }
948
+
949
+ #glide-modal .glide-btn-back {
950
+ position: absolute;
951
+ top: 16px;
952
+ left: 16px;
953
+ width: var(--glide-btn-size);
954
+ height: var(--glide-btn-size);
955
+ min-width: var(--glide-btn-size);
956
+ min-height: var(--glide-btn-size);
957
+ max-width: var(--glide-btn-size);
958
+ max-height: var(--glide-btn-size);
959
+ background: rgba(118, 118, 128, 0.12);
960
+ border: none;
961
+ border-radius: 50%;
962
+ display: flex;
963
+ align-items: center;
964
+ justify-content: center;
965
+ cursor: pointer;
966
+ color: rgba(0,0,0,0.5);
967
+ transition: background 0.2s;
968
+ z-index: 20;
969
+ padding: 0;
970
+ margin: 0;
971
+ box-sizing: border-box;
972
+ flex-shrink: 0;
973
+ }
974
+
975
+ #glide-modal.dark .glide-btn-back {
976
+ background: rgba(255, 255, 255, 0.1);
977
+ color: rgba(255,255,255,0.5);
978
+ }
979
+
980
+ .glide-btn-back:hover {
981
+ background: rgba(118, 118, 128, 0.2);
982
+ }
983
+
984
+ #glide-modal.dark .glide-btn-back:hover {
985
+ background: rgba(255, 255, 255, 0.2);
986
+ }
987
+
988
+ .glide-spinner {
989
+ width: 40px;
990
+ height: 40px;
991
+ border: 3px solid rgba(0,0,0,0.1);
992
+ border-top-color: var(--glide-primary);
993
+ border-radius: 50%;
994
+ animation: glide-spin 1s linear infinite;
995
+ margin: 40px auto;
996
+ }
997
+
998
+ #glide-modal.dark .glide-spinner {
999
+ border-color: rgba(255,255,255,0.1);
1000
+ border-top-color: var(--glide-primary);
1001
+ }
1002
+
1003
+ @keyframes glide-spin {
1004
+ to { transform: rotate(360deg); }
1005
+ }
1006
+ `;
1007
+ document.head.appendChild(styles);
1008
+ }
1009
+ show() {
1010
+ if (!this.container || !this.backdrop || this.isOpen) {
1011
+ if (this.isOpen && this.container && this.backdrop) {
1012
+ document.removeEventListener('keydown', this.handleEscapeKey);
1013
+ document.addEventListener('keydown', this.handleEscapeKey);
1014
+ }
1015
+ return;
1016
+ }
1017
+ document.body.appendChild(this.backdrop);
1018
+ document.body.appendChild(this.container);
1019
+ // Lock body scroll when modal opens
1020
+ this.lockBodyScroll();
1021
+ document.addEventListener('keydown', this.handleEscapeKey);
1022
+ // Note: setupPlatformToggles and setupHelpInteraction are called in render methods
1023
+ // Don't call them here to avoid duplicate event listeners
1024
+ requestAnimationFrame(() => {
1025
+ if (this.backdrop && this.container) {
1026
+ this.backdrop.style.opacity = '1';
1027
+ this.container.style.opacity = '1';
1028
+ this.container.style.transform = 'translateX(-50%) scale(1)';
1029
+ }
1030
+ });
1031
+ this.isOpen = true;
1032
+ }
1033
+ setupPlatformToggles() {
1034
+ const container = this.container?.querySelector('#glide-toggle');
1035
+ const platformBtns = this.container?.querySelectorAll('.glide-toggle-btn');
1036
+ const qrImg = this.container?.querySelector('#glide-qr-img');
1037
+ const message = this.container?.querySelector('#glide-platform-message');
1038
+ if (!platformBtns || !qrImg)
1039
+ return;
1040
+ platformBtns.forEach((btn) => {
1041
+ btn.addEventListener('click', (e) => {
1042
+ const target = e.currentTarget;
1043
+ const platform = target.getAttribute('data-platform');
1044
+ // Update container state for slider
1045
+ if (container && platform) {
1046
+ container.setAttribute('data-active', platform);
1047
+ }
1048
+ platformBtns.forEach(b => b.classList.remove('active'));
1049
+ target.classList.add('active');
1050
+ if (platform === 'ios') {
1051
+ qrImg.src = qrImg.getAttribute('data-ios') || '';
1052
+ if (message)
1053
+ message.textContent = 'Scan with your iPhone camera';
1054
+ }
1055
+ else if (platform === 'android') {
1056
+ qrImg.src = qrImg.getAttribute('data-android') || '';
1057
+ if (message)
1058
+ message.textContent = 'Scan with your Android camera';
1059
+ }
1060
+ });
1061
+ });
1062
+ }
1063
+ // --- Body Scroll Lock ---
1064
+ lockBodyScroll() {
1065
+ this.originalBodyOverflow = document.body.style.overflow;
1066
+ document.body.style.overflow = 'hidden';
1067
+ }
1068
+ unlockBodyScroll() {
1069
+ document.body.style.overflow = this.originalBodyOverflow;
1070
+ }
1071
+ close() {
1072
+ if (!this.container || !this.backdrop || !this.isOpen)
1073
+ return;
1074
+ this.isClosing = true;
1075
+ document.removeEventListener('keydown', this.handleEscapeKey);
1076
+ this.backdrop.style.opacity = '0';
1077
+ this.container.style.opacity = '0';
1078
+ this.container.style.transform = 'translateX(-50%) scale(0.94)';
1079
+ this.closeCallback = undefined;
1080
+ setTimeout(() => {
1081
+ // Only cleanup if we're still in closing state (not interrupted by new modal)
1082
+ if (this.isClosing) {
1083
+ this.cleanup();
1084
+ this.isOpen = false;
1085
+ this.isClosing = false;
1086
+ }
1087
+ }, 400);
1088
+ }
1089
+ cleanup() {
1090
+ this.container?.remove();
1091
+ this.backdrop?.remove();
1092
+ this.container = null;
1093
+ this.backdrop = null;
1094
+ // Unlock body scroll when modal closes
1095
+ this.unlockBodyScroll();
1096
+ }
1097
+ /**
1098
+ * Check if modal is currently open
1099
+ */
1100
+ isModalOpen() {
1101
+ return this.isOpen;
1102
+ }
1103
+ }
1104
+ exports.AuthModal = AuthModal;
1105
+ /**
1106
+ * Helper to create QRCodeData from DesktopData
1107
+ */
1108
+ function createQRCodeDataFromDesktop(desktopData) {
1109
+ const { data } = desktopData;
1110
+ // If we have platform-specific QRs
1111
+ if (data.ios_qr_image) {
1112
+ return {
1113
+ iosQRCode: data.ios_qr_image,
1114
+ androidQRCode: data.android_qr_image || data.ios_qr_image,
1115
+ };
1116
+ }
1117
+ // Fallback to single QR
1118
+ if (data.qr_code_image) {
1119
+ return data.qr_code_image;
1120
+ }
1121
+ throw new Error('No QR code data available');
1122
+ }