@zoreal/oauth2-react 0.1.11 → 0.2.5

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.
package/dist/index.js CHANGED
@@ -1,29 +1,755 @@
1
1
  'use client';
2
2
 
3
3
  // src/context.tsx
4
- import { createContext, useContext, useMemo } from "react";
4
+ import { createContext, useContext, useMemo, useState as useState2 } from "react";
5
5
 
6
6
  // src/wire.ts
7
7
  var WIRE_VERSION = 1;
8
- var SDK_VERSION = "0.1.11";
8
+ var SDK_VERSION = "0.2.5";
9
9
  var DEFAULT_ISSUER = "https://id.zoreal.com";
10
10
  var POLL_INTERVAL_MS = 2e3;
11
11
  var POLL_INTERVAL_ENROLLING_MS = 5e3;
12
12
 
13
+ // src/PairingModal.tsx
14
+ import { useEffect, useId, useRef, useState } from "react";
15
+ import { createPortal } from "react-dom";
16
+
17
+ // src/mark.tsx
18
+ import { jsx, jsxs } from "react/jsx-runtime";
19
+ var ZOREAL_BLUE = "#00b4d9";
20
+ function ZorealMark({
21
+ size = 18,
22
+ brand = false,
23
+ className
24
+ }) {
25
+ return /* @__PURE__ */ jsxs(
26
+ "svg",
27
+ {
28
+ width: size,
29
+ height: size,
30
+ viewBox: "8.4 7.4 62.2 62.2",
31
+ fill: brand ? ZOREAL_BLUE : "currentColor",
32
+ fillRule: "evenodd",
33
+ "aria-hidden": true,
34
+ focusable: "false",
35
+ className,
36
+ children: [
37
+ /* @__PURE__ */ jsx("path", { d: "M56.1,32.9c.6-3.1-.8-6.4-3.7-8.1l-18-10.4,5.2-3,15.4,8.9c5.4,3.1,7.7,9.6,5.8,15.3-.3.8-.6,1.6-1.1,2.4-.4.7-.9,1.4-1.5,2.1-1.3,1.4-2.9,2.6-4.6,3.3-3.6,1.5-7.9,1.4-11.6-.7l-8.9-5.1c-2.9-1.7-6.5-1.3-8.9.8-.6.6-1.2,1.2-1.7,2-.5.8-.8,1.7-.9,2.5-.6,3.1.9,6.4,3.7,8.1l18,10.4-5.2,3-15.4-8.9c-5.4-3.1-7.7-9.6-5.8-15.3.2-.8.6-1.6,1-2.4.5-.7,1-1.4,1.5-2.1,1.3-1.4,2.9-2.6,4.7-3.3,3.6-1.6,7.8-1.4,11.5.6l8.9,5.2c3,1.7,6.6,1.3,8.9-.8.6-.6,1.2-1.2,1.7-2,.4-.8.7-1.7.9-2.5Z" }),
38
+ /* @__PURE__ */ jsx("path", { d: "M68.7,44.2c-.7,1.2-2.3,1.7-3.5.9-1.3-.7-1.7-2.3-1-3.5.7-1.3,2.3-1.7,3.5-1,1.3.7,1.7,2.3,1,3.6Z" }),
39
+ /* @__PURE__ */ jsx("path", { d: "M25.6,21.3c5.1-.8,10.4,0,15.3,2.9l1.2.7h0c1.2.7,1.6,2.3.9,3.5s-2.3,1.7-3.5.9l-1.2-.7c-4.2-2.4-9.1-3-13.5-1.9-1.6.4-3.1,1.1-4.5,1.9h0c-1.2.7-2.8.3-3.5-1-.7-1.2-.3-2.8.9-3.5,0,0,.1,0,.3-.1.3-.1.6-.4,1-.5,2.1-1.1,4.4-1.7,6.7-2.2Z" }),
40
+ /* @__PURE__ */ jsx("path", { d: "M9.6,31.8c.7-1.2,2.4-1.6,3.5-.8,1.2.7,1.6,2.4.8,3.5-.8,1.2-2.4,1.6-3.6.8-1.2-.8-1.5-2.4-.7-3.5Z" }),
41
+ /* @__PURE__ */ jsx("path", { d: "M46.2,30.3c.7-1.3,2.3-1.7,3.5-1,1.2.7,1.7,2.3.9,3.6-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5Z" }),
42
+ /* @__PURE__ */ jsx("path", { d: "M52.1,54.5c-5,.9-10.4,0-15.3-2.8l-1.2-.7h0c-1.2-.7-1.7-2.3-.9-3.5s2.3-1.7,3.5-.9l1.2.7c4.3,2.4,9.1,3,13.6,1.9,1.6-.4,3.1-1.1,4.5-1.9h0c1.2-.7,2.8-.3,3.5.9.7,1.3.3,2.9-.9,3.6-.1,0-.2,0-.3,0-.4.2-.7.4-1.1.6-2.1,1-4.3,1.7-6.7,2.1Z" }),
43
+ /* @__PURE__ */ jsx("path", { d: "M31.4,45.6c-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5,2.3-1.7,3.5-.9,1.7,2.3.9,3.5Z" })
44
+ ]
45
+ }
46
+ );
47
+ }
48
+
49
+ // src/lockup.tsx
50
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
51
+ function ZorealLockup({ height = 22, className }) {
52
+ return /* @__PURE__ */ jsxs2(
53
+ "svg",
54
+ {
55
+ height,
56
+ width: height * (240 / 58.5),
57
+ viewBox: "0 0 240 58.5",
58
+ role: "img",
59
+ "aria-label": "ZOREAL",
60
+ focusable: "false",
61
+ className,
62
+ children: [
63
+ /* @__PURE__ */ jsx2(
64
+ "path",
65
+ {
66
+ fill: "currentColor",
67
+ d: "M205,40.5h15.3v-3.5h-11.5v-18.4h-3.8v21.8ZM157,22.2v5.6h10.9v3.5h-10.9v5.8h12.5v3.5h-16.3v-21.8h16.2v3.5h-12.4ZM141.4,25.8c0,1.1-.4,2-1.2,2.7-.8.7-1.9,1-3.3,1h-5.6v-7.3h5.6c1.4,0,2.6.3,3.4.9.8.6,1.2,1.5,1.2,2.7ZM146,40.5l-5.9-8.3c.8-.2,1.4-.5,2.1-.9s1.2-.9,1.7-1.4c.4-.5.8-1.2,1.1-1.9s.4-1.5.4-2.4-.1-2-.5-2.9c-.4-.9-.9-1.6-1.7-2.2-.6-.6-1.5-1-2.5-1.4-1-.3-2.2-.4-3.4-.4h-9.8v21.8h3.8v-7.6h4.8l5.4,7.6h4.5ZM115.7,29.7c0,1.1-.1,2.1-.5,3s-.9,1.7-1.5,2.4-1.4,1.2-2.4,1.7c-.9.4-1.9.6-3,.6s-2.1-.1-3-.6-1.7-1-2.4-1.7-1.2-1.5-1.5-2.4-.5-1.9-.5-3,.1-2.1.5-3,.9-1.7,1.5-2.4,1.4-1.2,2.4-1.7c.9-.4,1.9-.6,3-.6s2.1.2,3,.6c.9.4,1.7,1,2.3,1.7.6.6,1.2,1.5,1.6,2.4s.5,1.9.5,3ZM119.7,29.6c0-1.5-.3-3-.8-4.4-.6-1.4-1.4-2.5-2.4-3.6-1-1-2.2-1.8-3.6-2.4-1.4-.6-3-.9-4.6-.9s-3.2.4-4.6.9c-1.4.6-2.7,1.4-3.7,2.4s-1.8,2.2-2.4,3.6c-.5,1.4-.8,2.8-.8,4.4s.3,3,.8,4.4c.6,1.4,1.4,2.5,2.4,3.6,1,1,2.2,1.8,3.6,2.4,1.4.6,3,.9,4.6.9s3.2-.4,4.6-.9c1.4-.6,2.6-1.4,3.7-2.4,1-1,1.8-2.2,2.4-3.6.5-1.4.8-2.9.8-4.4ZM86.1,22.1l-13,15.6v2.8h17.9v-3.4h-12.9l12.9-15.6v-2.8h-17.5v3.4h12.5ZM188.5,18.5h-3.5l-9.6,22h4c3.7-8.8,3.4-8,7.4-17.4,3.7,8.8,3.9,9.1,7.4,17.4h4l-9.6-22Z"
68
+ }
69
+ ),
70
+ /* @__PURE__ */ jsxs2("g", { fill: ZOREAL_BLUE, fillRule: "evenodd", children: [
71
+ /* @__PURE__ */ jsx2("path", { d: "M52,25.7c.4-2-.5-4.2-2.5-5.4l-11.8-6.8,3.4-2,10.1,5.9c3.6,2,5.1,6.3,3.8,10.1-.2.5-.4,1-.7,1.6-.3.5-.6.9-1,1.4-.9.9-1.9,1.7-3,2.2-2.4,1-5.2.9-7.6-.5l-5.9-3.4c-1.9-1.1-4.3-.9-5.9.5-.4.4-.8.8-1.1,1.3-.3.5-.5,1.1-.6,1.7-.4,2,.6,4.2,2.5,5.4l11.8,6.8-3.4,2-10.1-5.9c-3.6-2-5.1-6.3-3.8-10.1.1-.5.4-1,.7-1.6.3-.5.7-.9,1-1.4.9-.9,1.9-1.7,3.1-2.2,2.4-1,5.2-.9,7.6.4l5.9,3.4c1.9,1.1,4.3.9,5.9-.5.4-.4.8-.8,1.1-1.3.3-.5.5-1.1.6-1.7Z" }),
72
+ /* @__PURE__ */ jsx2("path", { d: "M60.3,33.1c-.5.8-1.5,1.1-2.3.6-.9-.5-1.1-1.5-.7-2.3.5-.9,1.5-1.1,2.3-.7.9.5,1.1,1.5.7,2.4Z" }),
73
+ /* @__PURE__ */ jsx2("path", { d: "M31.9,18c3.4-.5,6.9,0,10,1.9l.8.5h0c.8.5,1,1.5.6,2.3s-1.5,1.1-2.3.6l-.8-.5c-2.8-1.6-6-1.9-8.9-1.2-1,.3-2,.7-3,1.2h0c-.8.5-1.8.2-2.3-.7-.5-.8-.2-1.8.6-2.3,0,0,0,0,.2,0,.2,0,.4-.2.7-.3,1.4-.7,2.9-1.1,4.4-1.4Z" }),
74
+ /* @__PURE__ */ jsx2("path", { d: "M21.4,24.9c.5-.8,1.6-1,2.3-.5.8.5,1,1.6.5,2.3-.5.8-1.6,1-2.4.5-.8-.5-1-1.6-.5-2.3Z" }),
75
+ /* @__PURE__ */ jsx2("path", { d: "M45.5,23.9c.5-.9,1.5-1.1,2.3-.7.8.5,1.1,1.5.6,2.4-.5.8-1.5,1.1-2.3.6s-1.1-1.5-.6-2.3Z" }),
76
+ /* @__PURE__ */ jsx2("path", { d: "M49.4,39.9c-3.3.6-6.9,0-10-1.8l-.8-.5h0c-.8-.5-1.1-1.5-.6-2.3s1.5-1.1,2.3-.6l.8.5c2.8,1.6,6,1.9,9,1.2,1-.3,2-.7,3-1.2h0c.8-.5,1.8-.2,2.3.6.5.9.2,1.9-.6,2.4,0,0-.1,0-.2,0-.2.1-.5.3-.7.4-1.4.7-2.8,1.1-4.4,1.4Z" }),
77
+ /* @__PURE__ */ jsx2("path", { d: "M35.8,34c-.5.8-1.5,1.1-2.3.6s-1.1-1.5-.6-2.3,1.5-1.1,2.3-.6,1.1,1.5.6,2.3Z" })
78
+ ] })
79
+ ]
80
+ }
81
+ );
82
+ }
83
+
84
+ // src/i18n.ts
85
+ var en = {
86
+ title: "Scan to sign in",
87
+ titleApprove: "Approve on your phone",
88
+ bodyScan: "Scan with your phone camera or the ZOREAL ID app.",
89
+ bodyApprove: "Approve the login in your ZOREAL ID app.",
90
+ bodyEnrolling: "Finish setting up ZOREAL ID on your phone, then approve the login.",
91
+ waiting: "Waiting for scan",
92
+ waitingApproval: "Waiting for approval",
93
+ expiresIn: "Expires in {time}",
94
+ secured: "Proof-of-Human verification by ZOREAL",
95
+ noIdTitle: "No ZOREAL ID yet?",
96
+ noIdBody: "Scan the same code to download the app and create one for free. It only takes a minute.",
97
+ cancel: "Cancel",
98
+ close: "Close",
99
+ qrAlt: "QR code to sign in with ZOREAL"
100
+ };
101
+ var TRANSLATIONS = {
102
+ en,
103
+ sv: {
104
+ title: "Skanna f\xF6r att logga in",
105
+ titleApprove: "Godk\xE4nn p\xE5 telefonen",
106
+ bodyScan: "Skanna med telefonens kamera eller ZOREAL ID-appen.",
107
+ bodyApprove: "Godk\xE4nn inloggningen i ZOREAL ID-appen.",
108
+ bodyEnrolling: "Slutf\xF6r konfigurationen av ZOREAL ID p\xE5 telefonen och godk\xE4nn sedan inloggningen.",
109
+ waiting: "V\xE4ntar p\xE5 skanning",
110
+ waitingApproval: "V\xE4ntar p\xE5 godk\xE4nnande",
111
+ expiresIn: "Upph\xF6r om {time}",
112
+ secured: "Proof-of-Human-verifiering av ZOREAL",
113
+ noIdTitle: "Har du inget ZOREAL ID?",
114
+ noIdBody: "Skanna samma kod f\xF6r att ladda ner appen och skapa ett gratis. Det tar bara en minut.",
115
+ cancel: "Avbryt",
116
+ close: "St\xE4ng",
117
+ qrAlt: "QR-kod f\xF6r att logga in med ZOREAL"
118
+ },
119
+ es: {
120
+ title: "Escanea para iniciar sesi\xF3n",
121
+ titleApprove: "Apru\xE9balo en tu tel\xE9fono",
122
+ bodyScan: "Escanea con la c\xE1mara de tu tel\xE9fono o con la app ZOREAL ID.",
123
+ bodyApprove: "Aprueba el inicio de sesi\xF3n en tu app ZOREAL ID.",
124
+ bodyEnrolling: "Termina de configurar ZOREAL ID en tu tel\xE9fono y luego aprueba el inicio de sesi\xF3n.",
125
+ waiting: "Esperando el escaneo",
126
+ waitingApproval: "Esperando aprobaci\xF3n",
127
+ expiresIn: "Caduca en {time}",
128
+ secured: "Verificaci\xF3n Proof-of-Human de ZOREAL",
129
+ noIdTitle: "\xBFA\xFAn no tienes ZOREAL ID?",
130
+ noIdBody: "Escanea el mismo c\xF3digo para descargar la app y crear una gratis. Solo toma un minuto.",
131
+ cancel: "Cancelar",
132
+ close: "Cerrar",
133
+ qrAlt: "C\xF3digo QR para iniciar sesi\xF3n con ZOREAL"
134
+ },
135
+ pt: {
136
+ title: "Digitalize para entrar",
137
+ titleApprove: "Aprove no seu telefone",
138
+ bodyScan: "Digitalize com a c\xE2mera do seu telefone ou com o app ZOREAL ID.",
139
+ bodyApprove: "Aprove o login no app ZOREAL ID.",
140
+ bodyEnrolling: "Termine de configurar o ZOREAL ID no seu telefone e depois aprove o login.",
141
+ waiting: "Aguardando digitaliza\xE7\xE3o",
142
+ waitingApproval: "Aguardando aprova\xE7\xE3o",
143
+ expiresIn: "Expira em {time}",
144
+ secured: "Verifica\xE7\xE3o Proof-of-Human da ZOREAL",
145
+ noIdTitle: "Ainda n\xE3o tem ZOREAL ID?",
146
+ noIdBody: "Digitalize o mesmo c\xF3digo para baixar o app e criar uma conta gr\xE1tis. Leva s\xF3 um minuto.",
147
+ cancel: "Cancelar",
148
+ close: "Fechar",
149
+ qrAlt: "C\xF3digo QR para entrar com ZOREAL"
150
+ },
151
+ fr: {
152
+ title: "Scannez pour vous connecter",
153
+ titleApprove: "Approuvez sur votre t\xE9l\xE9phone",
154
+ bodyScan: "Scannez avec l'appareil photo de votre t\xE9l\xE9phone ou l'app ZOREAL ID.",
155
+ bodyApprove: "Approuvez la connexion dans votre app ZOREAL ID.",
156
+ bodyEnrolling: "Terminez la configuration de ZOREAL ID sur votre t\xE9l\xE9phone, puis approuvez la connexion.",
157
+ waiting: "En attente du scan",
158
+ waitingApproval: "En attente d'approbation",
159
+ expiresIn: "Expire dans {time}",
160
+ secured: "V\xE9rification Proof-of-Human par ZOREAL",
161
+ noIdTitle: "Pas encore de ZOREAL ID ?",
162
+ noIdBody: "Scannez le m\xEAme code pour t\xE9l\xE9charger l'app et en cr\xE9er un gratuitement. Cela prend une minute.",
163
+ cancel: "Annuler",
164
+ close: "Fermer",
165
+ qrAlt: "Code QR pour se connecter avec ZOREAL"
166
+ },
167
+ de: {
168
+ title: "Zum Anmelden scannen",
169
+ titleApprove: "Auf dem Handy best\xE4tigen",
170
+ bodyScan: "Mit der Handykamera oder der ZOREAL ID App scannen.",
171
+ bodyApprove: "Anmeldung in der ZOREAL ID App best\xE4tigen.",
172
+ bodyEnrolling: "ZOREAL ID auf dem Handy fertig einrichten und dann die Anmeldung best\xE4tigen.",
173
+ waiting: "Warten auf Scan",
174
+ waitingApproval: "Warten auf Best\xE4tigung",
175
+ expiresIn: "L\xE4uft ab in {time}",
176
+ secured: "Proof-of-Human-Verifizierung von ZOREAL",
177
+ noIdTitle: "Noch keine ZOREAL ID?",
178
+ noIdBody: "Denselben Code scannen, um die App zu laden und kostenlos eine zu erstellen. Dauert nur eine Minute.",
179
+ cancel: "Abbrechen",
180
+ close: "Schlie\xDFen",
181
+ qrAlt: "QR-Code f\xFCr die Anmeldung mit ZOREAL"
182
+ },
183
+ ru: {
184
+ title: "\u041E\u0442\u0441\u043A\u0430\u043D\u0438\u0440\u0443\u0439\u0442\u0435, \u0447\u0442\u043E\u0431\u044B \u0432\u043E\u0439\u0442\u0438",
185
+ titleApprove: "\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u043D\u0430 \u0442\u0435\u043B\u0435\u0444\u043E\u043D\u0435",
186
+ bodyScan: "\u041E\u0442\u0441\u043A\u0430\u043D\u0438\u0440\u0443\u0439\u0442\u0435 \u043A\u0430\u043C\u0435\u0440\u043E\u0439 \u0442\u0435\u043B\u0435\u0444\u043E\u043D\u0430 \u0438\u043B\u0438 \u0447\u0435\u0440\u0435\u0437 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 ZOREAL ID.",
187
+ bodyApprove: "\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u0432\u0445\u043E\u0434 \u0432 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0438 ZOREAL ID.",
188
+ bodyEnrolling: "\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0443 ZOREAL ID \u043D\u0430 \u0442\u0435\u043B\u0435\u0444\u043E\u043D\u0435, \u0437\u0430\u0442\u0435\u043C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u0432\u0445\u043E\u0434.",
189
+ waiting: "\u041E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F",
190
+ waitingApproval: "\u041E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F",
191
+ expiresIn: "\u0418\u0441\u0442\u0435\u043A\u0430\u0435\u0442 \u0447\u0435\u0440\u0435\u0437 {time}",
192
+ secured: "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 Proof-of-Human \u043E\u0442 ZOREAL",
193
+ noIdTitle: "\u0415\u0449\u0451 \u043D\u0435\u0442 ZOREAL ID?",
194
+ noIdBody: "\u041E\u0442\u0441\u043A\u0430\u043D\u0438\u0440\u0443\u0439\u0442\u0435 \u0442\u043E\u0442 \u0436\u0435 \u043A\u043E\u0434, \u0447\u0442\u043E\u0431\u044B \u0441\u043A\u0430\u0447\u0430\u0442\u044C \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0438 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u0435\u0433\u043E \u0431\u0435\u0441\u043F\u043B\u0430\u0442\u043D\u043E. \u042D\u0442\u043E \u0437\u0430\u0439\u043C\u0451\u0442 \u043C\u0438\u043D\u0443\u0442\u0443.",
195
+ cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
196
+ close: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C",
197
+ qrAlt: "QR-\u043A\u043E\u0434 \u0434\u043B\u044F \u0432\u0445\u043E\u0434\u0430 \u0447\u0435\u0440\u0435\u0437 ZOREAL"
198
+ },
199
+ ja: {
200
+ title: "\u30B9\u30AD\u30E3\u30F3\u3057\u3066\u30ED\u30B0\u30A4\u30F3",
201
+ titleApprove: "\u30B9\u30DE\u30FC\u30C8\u30D5\u30A9\u30F3\u3067\u627F\u8A8D",
202
+ bodyScan: "\u30B9\u30DE\u30FC\u30C8\u30D5\u30A9\u30F3\u306E\u30AB\u30E1\u30E9\u307E\u305F\u306FZOREAL ID\u30A2\u30D7\u30EA\u3067\u30B9\u30AD\u30E3\u30F3\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
203
+ bodyApprove: "ZOREAL ID\u30A2\u30D7\u30EA\u3067\u30ED\u30B0\u30A4\u30F3\u3092\u627F\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
204
+ bodyEnrolling: "\u30B9\u30DE\u30FC\u30C8\u30D5\u30A9\u30F3\u3067ZOREAL ID\u306E\u8A2D\u5B9A\u3092\u5B8C\u4E86\u3057\u3001\u30ED\u30B0\u30A4\u30F3\u3092\u627F\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
205
+ waiting: "\u30B9\u30AD\u30E3\u30F3\u5F85\u3061",
206
+ waitingApproval: "\u627F\u8A8D\u5F85\u3061",
207
+ expiresIn: "\u6709\u52B9\u671F\u9650\u307E\u3067 {time}",
208
+ secured: "ZOREAL\u306B\u3088\u308BProof-of-Human\u8A8D\u8A3C",
209
+ noIdTitle: "ZOREAL ID\u3092\u304A\u6301\u3061\u3067\u306A\u3044\u3067\u3059\u304B\uFF1F",
210
+ noIdBody: "\u540C\u3058\u30B3\u30FC\u30C9\u3092\u30B9\u30AD\u30E3\u30F3\u3057\u3066\u30A2\u30D7\u30EA\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u3057\u3001\u7121\u6599\u3067\u4F5C\u6210\u3067\u304D\u307E\u3059\u30021\u5206\u307B\u3069\u3067\u5B8C\u4E86\u3057\u307E\u3059\u3002",
211
+ cancel: "\u30AD\u30E3\u30F3\u30BB\u30EB",
212
+ close: "\u9589\u3058\u308B",
213
+ qrAlt: "ZOREAL\u3067\u30ED\u30B0\u30A4\u30F3\u3059\u308B\u305F\u3081\u306EQR\u30B3\u30FC\u30C9"
214
+ },
215
+ hi: {
216
+ title: "\u0938\u093E\u0907\u0928 \u0907\u0928 \u0915\u0930\u0928\u0947 \u0915\u0947 \u0932\u093F\u090F \u0938\u094D\u0915\u0948\u0928 \u0915\u0930\u0947\u0902",
217
+ titleApprove: "\u0905\u092A\u0928\u0947 \u092B\u094B\u0928 \u092A\u0930 \u0938\u094D\u0935\u0940\u0915\u0943\u0924 \u0915\u0930\u0947\u0902",
218
+ bodyScan: "\u0905\u092A\u0928\u0947 \u092B\u094B\u0928 \u0915\u0947 \u0915\u0948\u092E\u0930\u0947 \u092F\u093E ZOREAL ID \u0910\u092A \u0938\u0947 \u0938\u094D\u0915\u0948\u0928 \u0915\u0930\u0947\u0902\u0964",
219
+ bodyApprove: "\u0905\u092A\u0928\u0947 ZOREAL ID \u0910\u092A \u092E\u0947\u0902 \u0932\u0949\u0917\u093F\u0928 \u0938\u094D\u0935\u0940\u0915\u0943\u0924 \u0915\u0930\u0947\u0902\u0964",
220
+ bodyEnrolling: "\u0905\u092A\u0928\u0947 \u092B\u094B\u0928 \u092A\u0930 ZOREAL ID \u0938\u0947\u091F\u0905\u092A \u092A\u0942\u0930\u093E \u0915\u0930\u0947\u0902, \u092B\u093F\u0930 \u0932\u0949\u0917\u093F\u0928 \u0938\u094D\u0935\u0940\u0915\u0943\u0924 \u0915\u0930\u0947\u0902\u0964",
221
+ waiting: "\u0938\u094D\u0915\u0948\u0928 \u0915\u0940 \u092A\u094D\u0930\u0924\u0940\u0915\u094D\u0937\u093E \u0939\u0948",
222
+ waitingApproval: "\u0938\u094D\u0935\u0940\u0915\u0943\u0924\u093F \u0915\u0940 \u092A\u094D\u0930\u0924\u0940\u0915\u094D\u0937\u093E \u0939\u0948",
223
+ expiresIn: "{time} \u092E\u0947\u0902 \u0938\u092E\u093E\u092A\u094D\u0924",
224
+ secured: "ZOREAL \u0926\u094D\u0935\u093E\u0930\u093E Proof-of-Human \u0938\u0924\u094D\u092F\u093E\u092A\u0928",
225
+ noIdTitle: "\u0905\u092D\u0940 \u0924\u0915 ZOREAL ID \u0928\u0939\u0940\u0902 \u0939\u0948?",
226
+ noIdBody: "\u0910\u092A \u0921\u093E\u0909\u0928\u0932\u094B\u0921 \u0915\u0930\u0928\u0947 \u0914\u0930 \u092E\u0941\u092B\u094D\u0924 \u092E\u0947\u0902 \u090F\u0915 \u092C\u0928\u093E\u0928\u0947 \u0915\u0947 \u0932\u093F\u090F \u0935\u0939\u0940 \u0915\u094B\u0921 \u0938\u094D\u0915\u0948\u0928 \u0915\u0930\u0947\u0902\u0964 \u0907\u0938\u092E\u0947\u0902 \u092C\u0938 \u090F\u0915 \u092E\u093F\u0928\u091F \u0932\u0917\u0924\u093E \u0939\u0948\u0964",
227
+ cancel: "\u0930\u0926\u094D\u0926 \u0915\u0930\u0947\u0902",
228
+ close: "\u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
229
+ qrAlt: "ZOREAL \u0938\u0947 \u0938\u093E\u0907\u0928 \u0907\u0928 \u0915\u0930\u0928\u0947 \u0915\u0947 \u0932\u093F\u090F QR \u0915\u094B\u0921"
230
+ },
231
+ zhs: {
232
+ title: "\u626B\u7801\u767B\u5F55",
233
+ titleApprove: "\u5728\u624B\u673A\u4E0A\u6279\u51C6",
234
+ bodyScan: "\u4F7F\u7528\u624B\u673A\u76F8\u673A\u6216 ZOREAL ID \u5E94\u7528\u626B\u63CF\u3002",
235
+ bodyApprove: "\u8BF7\u5728 ZOREAL ID \u5E94\u7528\u4E2D\u6279\u51C6\u767B\u5F55\u3002",
236
+ bodyEnrolling: "\u8BF7\u5728\u624B\u673A\u4E0A\u5B8C\u6210 ZOREAL ID \u8BBE\u7F6E\uFF0C\u7136\u540E\u6279\u51C6\u767B\u5F55\u3002",
237
+ waiting: "\u7B49\u5F85\u626B\u63CF",
238
+ waitingApproval: "\u7B49\u5F85\u6279\u51C6",
239
+ expiresIn: "{time} \u540E\u5931\u6548",
240
+ secured: "\u7531 ZOREAL \u63D0\u4F9B\u7684 Proof-of-Human \u9A8C\u8BC1",
241
+ noIdTitle: "\u8FD8\u6CA1\u6709 ZOREAL ID\uFF1F",
242
+ noIdBody: "\u626B\u63CF\u540C\u4E00\u4E2A\u4E8C\u7EF4\u7801\u5373\u53EF\u4E0B\u8F7D\u5E94\u7528\u5E76\u514D\u8D39\u521B\u5EFA\uFF0C\u53EA\u9700\u4E00\u5206\u949F\u3002",
243
+ cancel: "\u53D6\u6D88",
244
+ close: "\u5173\u95ED",
245
+ qrAlt: "\u4F7F\u7528 ZOREAL \u767B\u5F55\u7684\u4E8C\u7EF4\u7801"
246
+ },
247
+ zht: {
248
+ title: "\u6383\u78BC\u767B\u5165",
249
+ titleApprove: "\u5728\u624B\u6A5F\u4E0A\u6838\u51C6",
250
+ bodyScan: "\u4F7F\u7528\u624B\u6A5F\u76F8\u6A5F\u6216 ZOREAL ID \u61C9\u7528\u7A0B\u5F0F\u6383\u63CF\u3002",
251
+ bodyApprove: "\u8ACB\u5728 ZOREAL ID \u61C9\u7528\u7A0B\u5F0F\u4E2D\u6838\u51C6\u767B\u5165\u3002",
252
+ bodyEnrolling: "\u8ACB\u5728\u624B\u6A5F\u4E0A\u5B8C\u6210 ZOREAL ID \u8A2D\u5B9A\uFF0C\u7136\u5F8C\u6838\u51C6\u767B\u5165\u3002",
253
+ waiting: "\u7B49\u5F85\u6383\u63CF",
254
+ waitingApproval: "\u7B49\u5F85\u6838\u51C6",
255
+ expiresIn: "{time} \u5F8C\u5931\u6548",
256
+ secured: "\u7531 ZOREAL \u63D0\u4F9B\u7684 Proof-of-Human \u9A57\u8B49",
257
+ noIdTitle: "\u9084\u6C92\u6709 ZOREAL ID\uFF1F",
258
+ noIdBody: "\u6383\u63CF\u540C\u4E00\u500B QR code \u5373\u53EF\u4E0B\u8F09\u61C9\u7528\u7A0B\u5F0F\u4E26\u514D\u8CBB\u5EFA\u7ACB\uFF0C\u53EA\u9700\u4E00\u5206\u9418\u3002",
259
+ cancel: "\u53D6\u6D88",
260
+ close: "\u95DC\u9589",
261
+ qrAlt: "\u4F7F\u7528 ZOREAL \u767B\u5165\u7684 QR code"
262
+ },
263
+ ar: {
264
+ title: "\u0627\u0645\u0633\u062D \u0644\u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062F\u062E\u0648\u0644",
265
+ titleApprove: "\u0648\u0627\u0641\u0642 \u0639\u0644\u0649 \u0647\u0627\u062A\u0641\u0643",
266
+ bodyScan: "\u0627\u0645\u0633\u062D \u0628\u0627\u0633\u062A\u062E\u062F\u0627\u0645 \u0643\u0627\u0645\u064A\u0631\u0627 \u0647\u0627\u062A\u0641\u0643 \u0623\u0648 \u062A\u0637\u0628\u064A\u0642 ZOREAL ID.",
267
+ bodyApprove: "\u0648\u0627\u0641\u0642 \u0639\u0644\u0649 \u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062F\u062E\u0648\u0644 \u0641\u064A \u062A\u0637\u0628\u064A\u0642 ZOREAL ID.",
268
+ bodyEnrolling: "\u0623\u0643\u0645\u0644 \u0625\u0639\u062F\u0627\u062F ZOREAL ID \u0639\u0644\u0649 \u0647\u0627\u062A\u0641\u0643\u060C \u062B\u0645 \u0648\u0627\u0641\u0642 \u0639\u0644\u0649 \u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062F\u062E\u0648\u0644.",
269
+ waiting: "\u0641\u064A \u0627\u0646\u062A\u0638\u0627\u0631 \u0627\u0644\u0645\u0633\u062D",
270
+ waitingApproval: "\u0641\u064A \u0627\u0646\u062A\u0638\u0627\u0631 \u0627\u0644\u0645\u0648\u0627\u0641\u0642\u0629",
271
+ expiresIn: "\u062A\u0646\u062A\u0647\u064A \u0627\u0644\u0635\u0644\u0627\u062D\u064A\u0629 \u062E\u0644\u0627\u0644 {time}",
272
+ secured: "\u0627\u0644\u062A\u062D\u0642\u0642 \u0645\u0646 Proof-of-Human \u0628\u0648\u0627\u0633\u0637\u0629 ZOREAL",
273
+ noIdTitle: "\u0644\u064A\u0633 \u0644\u062F\u064A\u0643 ZOREAL ID \u0628\u0639\u062F\u061F",
274
+ noIdBody: "\u0627\u0645\u0633\u062D \u0627\u0644\u0631\u0645\u0632 \u0646\u0641\u0633\u0647 \u0644\u062A\u0646\u0632\u064A\u0644 \u0627\u0644\u062A\u0637\u0628\u064A\u0642 \u0648\u0625\u0646\u0634\u0627\u0621 \u062D\u0633\u0627\u0628 \u0645\u062C\u0627\u0646\u064A. \u064A\u0633\u062A\u063A\u0631\u0642 \u0627\u0644\u0623\u0645\u0631 \u062F\u0642\u064A\u0642\u0629 \u0648\u0627\u062D\u062F\u0629 \u0641\u0642\u0637.",
275
+ cancel: "\u0625\u0644\u063A\u0627\u0621",
276
+ close: "\u0625\u063A\u0644\u0627\u0642",
277
+ qrAlt: "\u0631\u0645\u0632 QR \u0644\u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062F\u062E\u0648\u0644 \u0628\u0627\u0633\u062A\u062E\u062F\u0627\u0645 ZOREAL"
278
+ },
279
+ ko: {
280
+ title: "\uC2A4\uCE94\uD558\uC5EC \uB85C\uADF8\uC778",
281
+ titleApprove: "\uD734\uB300\uD3F0\uC5D0\uC11C \uC2B9\uC778",
282
+ bodyScan: "\uD734\uB300\uD3F0 \uCE74\uBA54\uB77C \uB610\uB294 ZOREAL ID \uC571\uC73C\uB85C \uC2A4\uCE94\uD558\uC138\uC694.",
283
+ bodyApprove: "ZOREAL ID \uC571\uC5D0\uC11C \uB85C\uADF8\uC778\uC744 \uC2B9\uC778\uD558\uC138\uC694.",
284
+ bodyEnrolling: "\uD734\uB300\uD3F0\uC5D0\uC11C ZOREAL ID \uC124\uC815\uC744 \uC644\uB8CC\uD55C \uD6C4 \uB85C\uADF8\uC778\uC744 \uC2B9\uC778\uD558\uC138\uC694.",
285
+ waiting: "\uC2A4\uCE94 \uB300\uAE30 \uC911",
286
+ waitingApproval: "\uC2B9\uC778 \uB300\uAE30 \uC911",
287
+ expiresIn: "{time} \uD6C4 \uB9CC\uB8CC",
288
+ secured: "ZOREAL\uC758 Proof-of-Human \uC778\uC99D",
289
+ noIdTitle: "\uC544\uC9C1 ZOREAL ID\uAC00 \uC5C6\uC73C\uC2E0\uAC00\uC694?",
290
+ noIdBody: "\uAC19\uC740 \uCF54\uB4DC\uB97C \uC2A4\uCE94\uD574 \uC571\uC744 \uB0B4\uB824\uBC1B\uACE0 \uBB34\uB8CC\uB85C \uB9CC\uB4DC\uC138\uC694. 1\uBD84\uC774\uBA74 \uB429\uB2C8\uB2E4.",
291
+ cancel: "\uCDE8\uC18C",
292
+ close: "\uB2EB\uAE30",
293
+ qrAlt: "ZOREAL\uB85C \uB85C\uADF8\uC778\uD558\uAE30 \uC704\uD55C QR \uCF54\uB4DC"
294
+ }
295
+ };
296
+ var RTL = /* @__PURE__ */ new Set(["ar", "he", "fa", "ur"]);
297
+ function lookup(locale) {
298
+ const tag = locale.toLowerCase().replace(/_/g, "-");
299
+ const primary = tag.split("-")[0];
300
+ if (primary === "zh") {
301
+ const simplified = /(^|-)(hans|cn|sg|my)(-|$)/.test(tag);
302
+ return TRANSLATIONS[simplified ? "zhs" : "zht"];
303
+ }
304
+ return TRANSLATIONS[tag] ?? TRANSLATIONS[primary];
305
+ }
306
+ function browserLocales() {
307
+ if (typeof navigator === "undefined") return [];
308
+ const nav = navigator;
309
+ if (nav.languages && nav.languages.length) return [...nav.languages];
310
+ return nav.language ? [nav.language] : [];
311
+ }
312
+ function strings(locale) {
313
+ if (locale) return lookup(locale) ?? en;
314
+ for (const candidate of browserLocales()) {
315
+ const hit = lookup(candidate);
316
+ if (hit) return hit;
317
+ }
318
+ return en;
319
+ }
320
+ function isRtl(locale) {
321
+ const tag = locale ?? browserLocales()[0];
322
+ if (!tag) return false;
323
+ return RTL.has(tag.toLowerCase().replace(/_/g, "-").split("-")[0]);
324
+ }
325
+ function interpolate(template, time) {
326
+ return template.replace("{time}", time);
327
+ }
328
+
329
+ // src/styles.ts
330
+ var PREFIX = "zrl";
331
+ var cx = (name) => `${PREFIX}-${name}`;
332
+ var STYLE_ELEMENT_ID = "zoreal-pairing-styles";
333
+ var LIGHT = `
334
+ --zrl-scrim: rgba(16, 18, 27, 0.45);
335
+ --zrl-surface: #ffffff;
336
+ --zrl-surface-sunken: #f6f7f9;
337
+ --zrl-ink: #16181c;
338
+ --zrl-ink-soft: #4a4f57;
339
+ --zrl-ink-mute: #6b7078;
340
+ --zrl-line: #e4e6ea;
341
+ --zrl-line-soft: #eef0f3;
342
+ --zrl-accent: #00b4d9;
343
+ --zrl-accent-soft: #dcf3fa;
344
+ --zrl-accent-ink: #04698a;
345
+ --zrl-urgent: #b4761a;
346
+ --zrl-qr-bg: #ffffff;
347
+ --zrl-shadow: 0 1px 2px rgba(16, 18, 27, 0.06), 0 20px 50px -12px rgba(16, 18, 27, 0.3);
348
+ --zrl-ring: rgba(16, 18, 27, 0.07);
349
+ `;
350
+ var DARK = `
351
+ --zrl-scrim: rgba(0, 0, 0, 0.62);
352
+ --zrl-surface: #17191d;
353
+ --zrl-surface-sunken: #1f2226;
354
+ --zrl-ink: #f4f5f7;
355
+ --zrl-ink-soft: #b3b8c0;
356
+ --zrl-ink-mute: #8b9199;
357
+ --zrl-line: #2c3036;
358
+ --zrl-line-soft: #24272c;
359
+ --zrl-accent: #34c9e8;
360
+ --zrl-accent-soft: #0d3b47;
361
+ --zrl-accent-ink: #7fdcf0;
362
+ --zrl-urgent: #e0a952;
363
+ /* The QR well stays white in dark mode on purpose: a scanner needs the
364
+ quiet-zone contrast, and an inverted QR fails on a good number of phone
365
+ cameras. It reads as a deliberate light panel, not a theming miss. */
366
+ --zrl-qr-bg: #ffffff;
367
+ --zrl-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 20px 50px -12px rgba(0, 0, 0, 0.65);
368
+ --zrl-ring: rgba(255, 255, 255, 0.1);
369
+ `;
370
+ var CSS = `
371
+ .${PREFIX}-root { ${LIGHT} }
372
+ .${PREFIX}-root[data-theme="dark"] { ${DARK} }
373
+ @media (prefers-color-scheme: dark) {
374
+ .${PREFIX}-root[data-theme="auto"] { ${DARK} }
375
+ }
376
+
377
+ .${PREFIX}-scrim {
378
+ position: fixed;
379
+ inset: 0;
380
+ z-index: 2147483000;
381
+ display: grid;
382
+ place-items: center;
383
+ overflow-y: auto;
384
+ padding: 16px;
385
+ background: var(--zrl-scrim);
386
+ backdrop-filter: blur(3px);
387
+ -webkit-backdrop-filter: blur(3px);
388
+ font-family: inherit;
389
+ animation: ${PREFIX}-fade 200ms ease-out both;
390
+ }
391
+
392
+ .${PREFIX}-card {
393
+ position: relative;
394
+ box-sizing: border-box;
395
+ width: 100%;
396
+ max-width: 380px;
397
+ border-radius: 16px;
398
+ background: var(--zrl-surface);
399
+ color: var(--zrl-ink);
400
+ box-shadow: var(--zrl-shadow);
401
+ outline: 1px solid var(--zrl-ring);
402
+ outline-offset: -1px;
403
+ text-align: center;
404
+ animation: ${PREFIX}-rise 300ms cubic-bezier(0.23, 1, 0.32, 1) both;
405
+ }
406
+
407
+ .${PREFIX}-body { padding: 28px 24px 20px; }
408
+
409
+ .${PREFIX}-lockup { display: block; margin: 0 auto; color: var(--zrl-ink); }
410
+
411
+ .${PREFIX}-title {
412
+ margin: 18px 0 0;
413
+ font-size: 18px;
414
+ font-weight: 600;
415
+ letter-spacing: -0.01em;
416
+ line-height: 1.3;
417
+ color: var(--zrl-ink);
418
+ }
419
+
420
+ .${PREFIX}-body-text {
421
+ margin: 6px auto 0;
422
+ max-width: 30ch;
423
+ font-size: 14px;
424
+ line-height: 1.55;
425
+ color: var(--zrl-ink-soft);
426
+ }
427
+
428
+ .${PREFIX}-qr-well {
429
+ position: relative;
430
+ display: grid;
431
+ place-items: center;
432
+ box-sizing: border-box;
433
+ width: 204px;
434
+ height: 204px;
435
+ margin: 20px auto 0;
436
+ padding: 12px;
437
+ border: 1px solid var(--zrl-line);
438
+ border-radius: 16px;
439
+ background: var(--zrl-qr-bg);
440
+ }
441
+
442
+ .${PREFIX}-qr {
443
+ display: block;
444
+ width: 100%;
445
+ height: 100%;
446
+ border-radius: 8px;
447
+ transition: filter 300ms cubic-bezier(0.23, 1, 0.32, 1),
448
+ opacity 300ms cubic-bezier(0.23, 1, 0.32, 1),
449
+ transform 300ms cubic-bezier(0.23, 1, 0.32, 1);
450
+ }
451
+
452
+ /* Once the code is claimed the QR is spent. Blurring it out rather than
453
+ swapping it keeps one object on screen through the state change, so the eye
454
+ reads a transformation instead of two things trading places. */
455
+ .${PREFIX}-qr[data-spent="true"] { opacity: 0.2; filter: blur(3px); transform: scale(0.96); }
456
+
457
+ .${PREFIX}-qr-overlay {
458
+ position: absolute;
459
+ inset: 0;
460
+ display: grid;
461
+ place-items: center;
462
+ animation: ${PREFIX}-fade 200ms ease-out both;
463
+ }
464
+
465
+ .${PREFIX}-qr-badge {
466
+ display: grid;
467
+ place-items: center;
468
+ width: 56px;
469
+ height: 56px;
470
+ border-radius: 999px;
471
+ background: var(--zrl-accent-soft);
472
+ color: var(--zrl-accent-ink);
473
+ }
474
+
475
+ .${PREFIX}-status {
476
+ display: flex;
477
+ align-items: center;
478
+ justify-content: center;
479
+ gap: 8px;
480
+ margin-top: 20px;
481
+ font-size: 14px;
482
+ font-weight: 500;
483
+ color: var(--zrl-ink);
484
+ }
485
+
486
+ .${PREFIX}-dot { position: relative; display: grid; place-items: center; width: 8px; height: 8px; }
487
+ .${PREFIX}-dot i {
488
+ position: absolute;
489
+ width: 8px;
490
+ height: 8px;
491
+ border-radius: 999px;
492
+ background: var(--zrl-accent);
493
+ font-style: normal;
494
+ }
495
+ .${PREFIX}-dot i:first-child { animation: ${PREFIX}-ping 1.8s cubic-bezier(0.23, 1, 0.32, 1) infinite; }
496
+
497
+ .${PREFIX}-timer {
498
+ margin: 4px 0 0;
499
+ font-size: 12px;
500
+ font-variant-numeric: tabular-nums;
501
+ color: var(--zrl-ink-mute);
502
+ transition: color 200ms ease-out;
503
+ }
504
+ .${PREFIX}-timer[data-urgent="true"] { color: var(--zrl-urgent); }
505
+
506
+ .${PREFIX}-help {
507
+ padding: 14px 24px;
508
+ border-top: 1px solid var(--zrl-line-soft);
509
+ background: var(--zrl-surface-sunken);
510
+ border-radius: 0;
511
+ }
512
+ .${PREFIX}-help-title { margin: 0; font-size: 12px; font-weight: 600; color: var(--zrl-ink); }
513
+ .${PREFIX}-help-body {
514
+ margin: 4px auto 0;
515
+ max-width: 34ch;
516
+ font-size: 12px;
517
+ line-height: 1.55;
518
+ color: var(--zrl-ink-soft);
519
+ }
520
+
521
+ .${PREFIX}-footer { padding: 12px; border-top: 1px solid var(--zrl-line-soft); }
522
+
523
+ .${PREFIX}-cancel {
524
+ display: block;
525
+ width: 100%;
526
+ padding: 10px;
527
+ border: 0;
528
+ border-radius: 12px;
529
+ background: transparent;
530
+ font: inherit;
531
+ font-size: 14px;
532
+ font-weight: 500;
533
+ color: var(--zrl-ink-soft);
534
+ cursor: pointer;
535
+ transition: background-color 150ms ease-out, color 150ms ease-out, transform 150ms ease-out;
536
+ }
537
+ .${PREFIX}-cancel:hover { background: var(--zrl-surface-sunken); color: var(--zrl-ink); }
538
+ .${PREFIX}-cancel:active { transform: scale(0.99); }
539
+
540
+ .${PREFIX}-secured {
541
+ display: flex;
542
+ align-items: center;
543
+ justify-content: center;
544
+ gap: 6px;
545
+ margin: 6px 0 0;
546
+ font-size: 12px;
547
+ color: var(--zrl-ink-mute);
548
+ }
549
+
550
+ .${PREFIX}-close {
551
+ position: absolute;
552
+ top: 12px;
553
+ inset-inline-end: 12px;
554
+ display: grid;
555
+ place-items: center;
556
+ width: 32px;
557
+ height: 32px;
558
+ padding: 0;
559
+ border: 0;
560
+ border-radius: 8px;
561
+ background: transparent;
562
+ color: var(--zrl-ink-mute);
563
+ cursor: pointer;
564
+ transition: background-color 150ms ease-out, color 150ms ease-out, transform 150ms ease-out;
565
+ }
566
+ .${PREFIX}-close:hover { background: var(--zrl-surface-sunken); color: var(--zrl-ink); }
567
+ .${PREFIX}-close:active { transform: scale(0.95); }
568
+
569
+ .${PREFIX}-card :focus-visible {
570
+ outline: 2px solid var(--zrl-accent);
571
+ outline-offset: 2px;
572
+ }
573
+
574
+ @keyframes ${PREFIX}-fade { from { opacity: 0 } to { opacity: 1 } }
575
+ @keyframes ${PREFIX}-rise {
576
+ from { opacity: 0; transform: translateY(10px) scale(0.98) }
577
+ to { opacity: 1; transform: none }
578
+ }
579
+ @keyframes ${PREFIX}-ping {
580
+ 0% { transform: scale(1); opacity: 0.5 }
581
+ 70%, 100% { transform: scale(2.6); opacity: 0 }
582
+ }
583
+
584
+ @media (prefers-reduced-motion: reduce) {
585
+ .${PREFIX}-scrim,
586
+ .${PREFIX}-card,
587
+ .${PREFIX}-qr-overlay { animation: none }
588
+ .${PREFIX}-dot i:first-child { animation: none; opacity: 0.35 }
589
+ .${PREFIX}-qr,
590
+ .${PREFIX}-cancel,
591
+ .${PREFIX}-close,
592
+ .${PREFIX}-timer { transition: none }
593
+ }
594
+ `;
595
+ function ensureStyles() {
596
+ if (typeof document === "undefined") return;
597
+ if (document.getElementById(STYLE_ELEMENT_ID)) return;
598
+ const el = document.createElement("style");
599
+ el.id = STYLE_ELEMENT_ID;
600
+ el.textContent = CSS;
601
+ document.head.appendChild(el);
602
+ }
603
+
604
+ // src/PairingModal.tsx
605
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
606
+ var DEFAULT_PAIRING_TIMEOUT_MS = 12e4;
607
+ var URGENT_SECONDS = 20;
608
+ function mmss(totalSeconds) {
609
+ const m = Math.floor(totalSeconds / 60);
610
+ const s = totalSeconds % 60;
611
+ return `${m}:${String(s).padStart(2, "0")}`;
612
+ }
613
+ var IconClose = () => /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, focusable: "false", children: /* @__PURE__ */ jsx3("path", { d: "M18 6 6 18M6 6l12 12" }) });
614
+ var IconPhone = () => /* @__PURE__ */ jsxs3("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, focusable: "false", children: [
615
+ /* @__PURE__ */ jsx3("rect", { x: "6", y: "2", width: "12", height: "20", rx: "2.5" }),
616
+ /* @__PURE__ */ jsx3("path", { d: "M11 18.5h2" })
617
+ ] });
618
+ var IconShield = () => /* @__PURE__ */ jsxs3("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, focusable: "false", children: [
619
+ /* @__PURE__ */ jsx3("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }),
620
+ /* @__PURE__ */ jsx3("path", { d: "m9 12 2 2 4-4" })
621
+ ] });
622
+ function PairingModal({
623
+ state,
624
+ qrUrl,
625
+ onCancel,
626
+ locale,
627
+ theme = "auto",
628
+ timeoutMs = DEFAULT_PAIRING_TIMEOUT_MS
629
+ }) {
630
+ const t = strings(locale);
631
+ const titleId = useId();
632
+ const closeRef = useRef(null);
633
+ const deadlineRef = useRef(0);
634
+ const [remaining, setRemaining] = useState(Math.round(timeoutMs / 1e3));
635
+ const settled = state.status === "claimed" || state.status === "enrolling";
636
+ const onCancelRef = useRef(onCancel);
637
+ onCancelRef.current = onCancel;
638
+ useEffect(() => {
639
+ ensureStyles();
640
+ }, []);
641
+ useEffect(() => {
642
+ const serverMs = typeof state.expiresIn === "number" ? state.expiresIn * 1e3 : Infinity;
643
+ deadlineRef.current = Date.now() + Math.min(timeoutMs, serverMs);
644
+ setRemaining(Math.round(Math.min(timeoutMs, serverMs) / 1e3));
645
+ const id = setInterval(() => {
646
+ const left = Math.max(0, Math.ceil((deadlineRef.current - Date.now()) / 1e3));
647
+ setRemaining(left);
648
+ if (left === 0) {
649
+ clearInterval(id);
650
+ onCancelRef.current();
651
+ }
652
+ }, 1e3);
653
+ return () => clearInterval(id);
654
+ }, [timeoutMs]);
655
+ useEffect(() => {
656
+ const onKey = (e) => {
657
+ if (e.key === "Escape") onCancelRef.current();
658
+ };
659
+ document.addEventListener("keydown", onKey);
660
+ const previous = document.body.style.overflow;
661
+ document.body.style.overflow = "hidden";
662
+ closeRef.current?.focus();
663
+ return () => {
664
+ document.removeEventListener("keydown", onKey);
665
+ document.body.style.overflow = previous;
666
+ };
667
+ }, []);
668
+ if (typeof document === "undefined") return null;
669
+ const body = state.status === "enrolling" ? t.bodyEnrolling : settled ? t.bodyApprove : t.bodyScan;
670
+ return createPortal(
671
+ /* @__PURE__ */ jsx3("div", { className: `${cx("root")} ${cx("scrim")}`, "data-theme": theme, onClick: onCancel, children: /* @__PURE__ */ jsxs3(
672
+ "div",
673
+ {
674
+ className: cx("card"),
675
+ role: "dialog",
676
+ "aria-modal": "true",
677
+ "aria-labelledby": titleId,
678
+ dir: isRtl(locale) ? "rtl" : "ltr",
679
+ onClick: (e) => e.stopPropagation(),
680
+ children: [
681
+ /* @__PURE__ */ jsx3("button", { ref: closeRef, type: "button", className: cx("close"), "aria-label": t.close, onClick: onCancel, children: /* @__PURE__ */ jsx3(IconClose, {}) }),
682
+ /* @__PURE__ */ jsxs3("div", { className: cx("body"), children: [
683
+ /* @__PURE__ */ jsx3(ZorealLockup, { height: 44, className: cx("lockup") }),
684
+ /* @__PURE__ */ jsx3("h2", { id: titleId, className: cx("title"), children: settled ? t.titleApprove : t.title }),
685
+ /* @__PURE__ */ jsx3("p", { className: cx("body-text"), children: body }),
686
+ /* @__PURE__ */ jsxs3("div", { className: cx("qr-well"), children: [
687
+ /* @__PURE__ */ jsx3("img", { className: cx("qr"), "data-spent": settled, src: qrUrl, alt: t.qrAlt, width: 180, height: 180 }),
688
+ settled && /* @__PURE__ */ jsx3("span", { className: cx("qr-overlay"), children: /* @__PURE__ */ jsx3("span", { className: cx("qr-badge"), children: /* @__PURE__ */ jsx3(IconPhone, {}) }) })
689
+ ] }),
690
+ /* @__PURE__ */ jsxs3("div", { className: cx("status"), children: [
691
+ /* @__PURE__ */ jsxs3("span", { className: cx("dot"), children: [
692
+ /* @__PURE__ */ jsx3("i", {}),
693
+ /* @__PURE__ */ jsx3("i", {})
694
+ ] }),
695
+ settled ? t.waitingApproval : t.waiting
696
+ ] }),
697
+ /* @__PURE__ */ jsx3("p", { className: cx("timer"), "data-urgent": remaining <= URGENT_SECONDS, children: interpolate(t.expiresIn, mmss(remaining)) })
698
+ ] }),
699
+ /* @__PURE__ */ jsxs3("div", { className: cx("help"), children: [
700
+ /* @__PURE__ */ jsx3("p", { className: cx("help-title"), children: t.noIdTitle }),
701
+ /* @__PURE__ */ jsx3("p", { className: cx("help-body"), children: t.noIdBody })
702
+ ] }),
703
+ /* @__PURE__ */ jsxs3("div", { className: cx("footer"), children: [
704
+ /* @__PURE__ */ jsx3("button", { type: "button", className: cx("cancel"), onClick: onCancel, children: t.cancel }),
705
+ /* @__PURE__ */ jsxs3("p", { className: cx("secured"), children: [
706
+ /* @__PURE__ */ jsx3(IconShield, {}),
707
+ t.secured
708
+ ] })
709
+ ] })
710
+ ]
711
+ }
712
+ ) }),
713
+ document.body
714
+ );
715
+ }
716
+
13
717
  // src/context.tsx
14
- import { jsx } from "react/jsx-runtime";
718
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
15
719
  var ZorealOAuthContext = createContext(null);
720
+ var PairingHostContext = createContext(null);
721
+ function useZorealPairingHost() {
722
+ return useContext(PairingHostContext);
723
+ }
16
724
  function ZorealOAuthProvider({
17
725
  clientId,
18
726
  issuer = DEFAULT_ISSUER,
19
727
  locale,
728
+ theme = "auto",
729
+ pairingUI = "modal",
730
+ pairingTimeoutMs,
20
731
  children
21
732
  }) {
733
+ const [pairing, setPairing] = useState2(null);
22
734
  const value = useMemo(
23
735
  () => ({ clientId, issuer: issuer.replace(/\/$/, ""), locale }),
24
736
  [clientId, issuer, locale]
25
737
  );
26
- return /* @__PURE__ */ jsx(ZorealOAuthContext.Provider, { value, children });
738
+ const host = pairingUI === "modal" ? setPairing : null;
739
+ return /* @__PURE__ */ jsx4(ZorealOAuthContext.Provider, { value, children: /* @__PURE__ */ jsxs4(PairingHostContext.Provider, { value: host, children: [
740
+ children,
741
+ pairing && /* @__PURE__ */ jsx4(
742
+ PairingModal,
743
+ {
744
+ state: pairing.state,
745
+ qrUrl: pairing.qrUrl,
746
+ onCancel: pairing.cancel,
747
+ locale,
748
+ theme,
749
+ timeoutMs: pairingTimeoutMs
750
+ }
751
+ )
752
+ ] }) });
27
753
  }
28
754
  function useZorealOAuth() {
29
755
  const ctx = useContext(ZorealOAuthContext);
@@ -39,7 +765,7 @@ function useZorealOAuth() {
39
765
  import { useMemo as useMemo2 } from "react";
40
766
 
41
767
  // src/useZorealLogin.ts
42
- import { useCallback, useEffect, useRef, useState } from "react";
768
+ import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
43
769
 
44
770
  // src/jwt.ts
45
771
  function unsafeClaims(idToken) {
@@ -193,11 +919,20 @@ function generateState() {
193
919
  // src/useZorealLogin.ts
194
920
  function useZorealFlow(options) {
195
921
  const { clientId, issuer, locale } = useZorealOAuth();
196
- const [pairing, setPairing] = useState(null);
197
- const abortRef = useRef(null);
198
- const optionsRef = useRef(options);
922
+ const [pairing, setPairing] = useState3(null);
923
+ const publish = useZorealPairingHost();
924
+ const publishRef = useRef2(publish);
925
+ publishRef.current = publish;
926
+ const abortRef = useRef2(null);
927
+ const optionsRef = useRef2(options);
199
928
  optionsRef.current = options;
200
- useEffect(() => () => abortRef.current?.abort(), []);
929
+ useEffect2(
930
+ () => () => {
931
+ abortRef.current?.abort();
932
+ publishRef.current?.(null);
933
+ },
934
+ []
935
+ );
201
936
  const login = useCallback(() => {
202
937
  const opts = optionsRef.current;
203
938
  const run = async () => {
@@ -232,6 +967,7 @@ function useZorealFlow(options) {
232
967
  const cancel = () => {
233
968
  controller.abort();
234
969
  setPairing(null);
970
+ publishRef.current?.(null);
235
971
  };
236
972
  const surface = {
237
973
  pairUrl: started.pair_url,
@@ -248,6 +984,9 @@ function useZorealFlow(options) {
248
984
  cancel
249
985
  };
250
986
  setPairing(active);
987
+ if (!useAppLink) {
988
+ publishRef.current?.({ state: active.state, qrUrl: surface.qrUrl, cancel });
989
+ }
251
990
  opts.onPairingStateChange?.(active.state);
252
991
  if (useAppLink) {
253
992
  window.location.assign(started.pair_url);
@@ -260,12 +999,16 @@ function useZorealFlow(options) {
260
999
  setPairing(
261
1000
  (p) => p && p.requestId === started.request_id ? { ...p, state: enriched } : p
262
1001
  );
1002
+ if (!useAppLink) {
1003
+ publishRef.current?.({ state: enriched, qrUrl: surface.qrUrl, cancel });
1004
+ }
263
1005
  opts.onPairingStateChange?.(enriched);
264
1006
  },
265
1007
  controller.signal
266
1008
  );
267
1009
  }
268
1010
  setPairing(null);
1011
+ publishRef.current?.(null);
269
1012
  if (flow === "auth-code") {
270
1013
  opts.onCode?.({
271
1014
  code,
@@ -291,6 +1034,7 @@ function useZorealFlow(options) {
291
1034
  opts.onCredential?.(response);
292
1035
  } catch (e) {
293
1036
  setPairing(null);
1037
+ publishRef.current?.(null);
294
1038
  if (e instanceof DOMException && e.name === "AbortError") return;
295
1039
  if (e instanceof FlowAbandonedError) {
296
1040
  opts.onNonOAuthError?.(e.reason);
@@ -326,7 +1070,7 @@ function useZorealLogin(options) {
326
1070
  }
327
1071
 
328
1072
  // src/ZorealLogin.tsx
329
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
1073
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
330
1074
  var TEXTS = {
331
1075
  continue_with: "Continue with ZOREAL",
332
1076
  signin_with: "Sign in with ZOREAL",
@@ -338,80 +1082,6 @@ var SIZES = {
338
1082
  medium: { height: 38, font: 14, pad: 16 },
339
1083
  small: { height: 32, font: 12, pad: 12 }
340
1084
  };
341
- var Mark = ({ size }) => /* @__PURE__ */ jsxs(
342
- "svg",
343
- {
344
- width: size,
345
- height: size,
346
- viewBox: "8.4 7.4 62.2 62.2",
347
- fill: "currentColor",
348
- fillRule: "evenodd",
349
- "aria-hidden": true,
350
- focusable: "false",
351
- children: [
352
- /* @__PURE__ */ jsx2("path", { d: "M56.1,32.9c.6-3.1-.8-6.4-3.7-8.1l-18-10.4,5.2-3,15.4,8.9c5.4,3.1,7.7,9.6,5.8,15.3-.3.8-.6,1.6-1.1,2.4-.4.7-.9,1.4-1.5,2.1-1.3,1.4-2.9,2.6-4.6,3.3-3.6,1.5-7.9,1.4-11.6-.7l-8.9-5.1c-2.9-1.7-6.5-1.3-8.9.8-.6.6-1.2,1.2-1.7,2-.5.8-.8,1.7-.9,2.5-.6,3.1.9,6.4,3.7,8.1l18,10.4-5.2,3-15.4-8.9c-5.4-3.1-7.7-9.6-5.8-15.3.2-.8.6-1.6,1-2.4.5-.7,1-1.4,1.5-2.1,1.3-1.4,2.9-2.6,4.7-3.3,3.6-1.6,7.8-1.4,11.5.6l8.9,5.2c3,1.7,6.6,1.3,8.9-.8.6-.6,1.2-1.2,1.7-2,.4-.8.7-1.7.9-2.5Z" }),
353
- /* @__PURE__ */ jsx2("path", { d: "M68.7,44.2c-.7,1.2-2.3,1.7-3.5.9-1.3-.7-1.7-2.3-1-3.5.7-1.3,2.3-1.7,3.5-1,1.3.7,1.7,2.3,1,3.6Z" }),
354
- /* @__PURE__ */ jsx2("path", { d: "M25.6,21.3c5.1-.8,10.4,0,15.3,2.9l1.2.7h0c1.2.7,1.6,2.3.9,3.5s-2.3,1.7-3.5.9l-1.2-.7c-4.2-2.4-9.1-3-13.5-1.9-1.6.4-3.1,1.1-4.5,1.9h0c-1.2.7-2.8.3-3.5-1-.7-1.2-.3-2.8.9-3.5,0,0,.1,0,.3-.1.3-.1.6-.4,1-.5,2.1-1.1,4.4-1.7,6.7-2.2Z" }),
355
- /* @__PURE__ */ jsx2("path", { d: "M9.6,31.8c.7-1.2,2.4-1.6,3.5-.8,1.2.7,1.6,2.4.8,3.5-.8,1.2-2.4,1.6-3.6.8-1.2-.8-1.5-2.4-.7-3.5Z" }),
356
- /* @__PURE__ */ jsx2("path", { d: "M46.2,30.3c.7-1.3,2.3-1.7,3.5-1,1.2.7,1.7,2.3.9,3.6-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5Z" }),
357
- /* @__PURE__ */ jsx2("path", { d: "M52.1,54.5c-5,.9-10.4,0-15.3-2.8l-1.2-.7h0c-1.2-.7-1.7-2.3-.9-3.5s2.3-1.7,3.5-.9l1.2.7c4.3,2.4,9.1,3,13.6,1.9,1.6-.4,3.1-1.1,4.5-1.9h0c1.2-.7,2.8-.3,3.5.9.7,1.3.3,2.9-.9,3.6-.1,0-.2,0-.3,0-.4.2-.7.4-1.1.6-2.1,1-4.3,1.7-6.7,2.1Z" }),
358
- /* @__PURE__ */ jsx2("path", { d: "M31.4,45.6c-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5,2.3-1.7,3.5-.9,1.7,2.3.9,3.5Z" })
359
- ]
360
- }
361
- );
362
- var PairingPanel = ({ pairing }) => {
363
- const { status } = pairing.state;
364
- const line = status === "claimed" ? "Approve the login in your ZOREAL ID app." : status === "enrolling" ? "Finishing enrolment. This screen will continue by itself." : pairing.appLink ? "Continue in the ZOREAL ID app, then return to this tab." : "Scan with your phone camera or the ZOREAL ID app.";
365
- return /* @__PURE__ */ jsxs(
366
- "div",
367
- {
368
- role: "dialog",
369
- "aria-label": "Log in with ZOREAL",
370
- style: {
371
- marginTop: 8,
372
- padding: 16,
373
- width: 232,
374
- borderRadius: 12,
375
- border: "1px solid rgba(128,128,128,0.35)",
376
- background: "Canvas",
377
- color: "CanvasText",
378
- textAlign: "center",
379
- fontFamily: "inherit"
380
- },
381
- children: [
382
- !pairing.appLink && /* @__PURE__ */ jsx2(
383
- "img",
384
- {
385
- src: pairing.qrUrl,
386
- alt: `QR code for ${pairing.pairUrl}`,
387
- width: 200,
388
- height: 200,
389
- style: { display: "block", margin: "0 auto", borderRadius: 8 }
390
- }
391
- ),
392
- /* @__PURE__ */ jsx2("p", { style: { margin: "10px 0 0", fontSize: 12, lineHeight: 1.5 }, children: line }),
393
- /* @__PURE__ */ jsx2(
394
- "button",
395
- {
396
- type: "button",
397
- onClick: pairing.cancel,
398
- style: {
399
- marginTop: 10,
400
- border: "none",
401
- background: "none",
402
- color: "inherit",
403
- opacity: 0.6,
404
- fontSize: 12,
405
- cursor: "pointer",
406
- textDecoration: "underline"
407
- },
408
- children: "Cancel"
409
- }
410
- )
411
- ]
412
- }
413
- );
414
- };
415
1085
  function ZorealLogin(props) {
416
1086
  const {
417
1087
  onSuccess,
@@ -427,7 +1097,7 @@ function ZorealLogin(props) {
427
1097
  click_listener,
428
1098
  ...request
429
1099
  } = props;
430
- const { login, internals } = useZorealFlow({
1100
+ const { login } = useZorealFlow({
431
1101
  ...request,
432
1102
  flow: "browser-direct",
433
1103
  onCredential: onSuccess,
@@ -453,28 +1123,25 @@ function ZorealLogin(props) {
453
1123
  }),
454
1124
  [logo_alignment, s, shape, theme, width]
455
1125
  );
456
- return /* @__PURE__ */ jsxs("div", { ...containerProps, children: [
457
- /* @__PURE__ */ jsxs(
458
- "button",
459
- {
460
- type: "button",
461
- style,
462
- onClick: () => {
463
- click_listener?.();
464
- login();
465
- },
466
- children: [
467
- /* @__PURE__ */ jsx2(Mark, { size: Math.round(s.font * 1.25) }),
468
- type === "standard" && TEXTS[text]
469
- ]
470
- }
471
- ),
472
- internals.pairing && !internals.pairing.appLink && /* @__PURE__ */ jsx2(PairingPanel, { pairing: internals.pairing })
473
- ] });
1126
+ return /* @__PURE__ */ jsx5("div", { ...containerProps, children: /* @__PURE__ */ jsxs5(
1127
+ "button",
1128
+ {
1129
+ type: "button",
1130
+ style,
1131
+ onClick: () => {
1132
+ click_listener?.();
1133
+ login();
1134
+ },
1135
+ children: [
1136
+ /* @__PURE__ */ jsx5(ZorealMark, { size: Math.round(s.font * 1.25) }),
1137
+ type === "standard" && TEXTS[text]
1138
+ ]
1139
+ }
1140
+ ) });
474
1141
  }
475
1142
 
476
1143
  // src/useZorealAutoLogin.ts
477
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
1144
+ import { useEffect as useEffect3, useRef as useRef3 } from "react";
478
1145
  function useZorealAutoLogin(options) {
479
1146
  const { login } = useZorealFlow({
480
1147
  flow: "browser-direct",
@@ -491,8 +1158,8 @@ function useZorealAutoLogin(options) {
491
1158
  },
492
1159
  onNonOAuthError: (e) => options.onError?.(e)
493
1160
  });
494
- const fired = useRef2(false);
495
- useEffect2(() => {
1161
+ const fired = useRef3(false);
1162
+ useEffect3(() => {
496
1163
  if (options.disabled || fired.current) return;
497
1164
  fired.current = true;
498
1165
  login();
@@ -513,6 +1180,8 @@ function hasGrantedAnyScopeZoreal(response, firstScope, ...restScopes) {
513
1180
  return [firstScope, ...restScopes].some((s) => granted.has(s));
514
1181
  }
515
1182
  export {
1183
+ DEFAULT_PAIRING_TIMEOUT_MS,
1184
+ PairingModal,
516
1185
  ZorealLogin,
517
1186
  ZorealOAuthProvider,
518
1187
  hasGrantedAllScopesZoreal,