@neofaceid/web-sdk 1.31.0 → 1.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1626,7 +1626,7 @@ export declare const validateToken: (applicationToken: string) => Promise<boolea
1626
1626
  * MINOR: Incrementado quando adicionamos funcionalidades mantendo compatibilidade
1627
1627
  * PATCH: Incrementado quando corrigimos bugs mantendo compatibilidade
1628
1628
  */
1629
- export declare const VERSION = "1.31.0";
1629
+ export declare const VERSION = "1.33.0";
1630
1630
 
1631
1631
  /**
1632
1632
  * Executa `fn(sessionId)`. Se o servidor devolver 410 (sessão consumida/expirada),
@@ -4362,6 +4362,12 @@ const INK = {
4362
4362
  900: "#111C2B",
4363
4363
  950: "#0A1320"
4364
4364
  };
4365
+ const SEMANTIC = {
4366
+ success: { fg: "#046C4E", bg: "#E7F7F0", solid: "#0E9F6E" },
4367
+ attention: { fg: "#6E5600", bg: "#FFF6D9", solid: "#F1BE00" },
4368
+ critical: { fg: "#9B1C1C", bg: "#FDEAEA", solid: "#E02424" },
4369
+ info: { fg: "#00449A", bg: "#EBF6FE", solid: "#0073F4" }
4370
+ };
4365
4371
  const LIGHT_TOKENS = {
4366
4372
  ground: INK[25],
4367
4373
  surface: "#FFFFFF",
@@ -10059,7 +10065,7 @@ class EmailPasswordModal {
10059
10065
  }
10060
10066
  }
10061
10067
  }
10062
- const MAX_ATTEMPTS = 2;
10068
+ const MAX_ATTEMPTS$1 = 2;
10063
10069
  const RETRY_DELAY = 500;
10064
10070
  const CAMERA_READY_DELAY = 200;
10065
10071
  const CAMERA_STABILITY_DELAY = 100;
@@ -10477,7 +10483,7 @@ async function executeBiometricLoginFlow(options) {
10477
10483
  ErrorType.UNKNOWN
10478
10484
  );
10479
10485
  overlay.updateStatus("error");
10480
- if (attempts < MAX_ATTEMPTS) {
10486
+ if (attempts < MAX_ATTEMPTS$1) {
10481
10487
  setTimeout(() => {
10482
10488
  tryLogin();
10483
10489
  }, RETRY_DELAY);
@@ -10860,7 +10866,7 @@ const biometricDetection = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.
10860
10866
  initializeBiometricDetection,
10861
10867
  isAdvancedDetectionAvailable
10862
10868
  }, Symbol.toStringTag, { value: "Module" }));
10863
- const VERSION = "1.31.0";
10869
+ const VERSION = "1.33.0";
10864
10870
  const RELEASE_DATE = "2026-09-02";
10865
10871
  let cachedSession = null;
10866
10872
  function getCachedCaptureSession() {
@@ -12153,6 +12159,306 @@ function pointsFor(g) {
12153
12159
  return ["24 24", "24 24", "24 24"];
12154
12160
  }
12155
12161
  }
12162
+ function StatusPill({ tone, children, icon, className }) {
12163
+ const palette = SEMANTIC[tone];
12164
+ return /* @__PURE__ */ jsxs(
12165
+ "span",
12166
+ {
12167
+ className,
12168
+ style: {
12169
+ display: "inline-flex",
12170
+ alignItems: "center",
12171
+ gap: 6,
12172
+ padding: "4px 10px",
12173
+ borderRadius: 9999,
12174
+ background: palette.bg,
12175
+ color: palette.fg,
12176
+ fontSize: 13,
12177
+ fontWeight: 600,
12178
+ fontFamily: "inherit",
12179
+ lineHeight: 1.3
12180
+ },
12181
+ children: [
12182
+ icon,
12183
+ children
12184
+ ]
12185
+ }
12186
+ );
12187
+ }
12188
+ const DEFAULT = {
12189
+ title: "Não deu certo desta vez",
12190
+ hint: "Tente se posicionar em um ambiente mais estável.",
12191
+ actions: [
12192
+ "Segure o dispositivo firme",
12193
+ "Fique de frente para uma luz suave"
12194
+ ]
12195
+ };
12196
+ const MAP = {
12197
+ LIVENESS_LOW: {
12198
+ title: "Precisamos ver com mais nitidez",
12199
+ hint: "A prova de vida veio abaixo do esperado.",
12200
+ actions: [
12201
+ "Faça o movimento com calma até o fim",
12202
+ "Aproxime o rosto sem cortar as bordas"
12203
+ ]
12204
+ },
12205
+ NO_FACE_DETECTED: {
12206
+ title: "Não vi seu rosto",
12207
+ hint: "A câmera não conseguiu identificar um rosto na moldura.",
12208
+ actions: [
12209
+ "Centralize o rosto no oval",
12210
+ "Afaste-se um pouco se estiver muito próximo"
12211
+ ]
12212
+ },
12213
+ LOW_LIGHT: {
12214
+ title: "A luz está baixa demais",
12215
+ hint: "Falta contraste para o algoritmo distinguir seu rosto.",
12216
+ actions: [
12217
+ "Fique de frente para uma janela ou lâmpada",
12218
+ "Evite luz vindo por trás — deixa o rosto em sombra"
12219
+ ]
12220
+ },
12221
+ LOOK_STRAIGHT: {
12222
+ title: "Olhe direto para a câmera",
12223
+ hint: "O rosto está um pouco de lado para reconhecimento facial.",
12224
+ actions: [
12225
+ "Fique de frente para a câmera",
12226
+ "Mantenha o queixo paralelo ao chão"
12227
+ ]
12228
+ },
12229
+ RECOGNITION_FAILED: {
12230
+ title: "Não consegui confirmar",
12231
+ hint: "A imagem não bate com o cadastro que temos.",
12232
+ actions: [
12233
+ "Retire óculos ou máscara temporariamente",
12234
+ "Faça em um lugar mais iluminado"
12235
+ ]
12236
+ },
12237
+ INVALID_TOKEN: {
12238
+ title: "Sessão expirada",
12239
+ hint: "A conexão com o serviço perdeu validade.",
12240
+ actions: [
12241
+ "Volte à tela anterior e tente novamente",
12242
+ "Se persistir, entre em contato com o suporte"
12243
+ ]
12244
+ },
12245
+ NETWORK: {
12246
+ title: "A conexão está oscilando",
12247
+ hint: "A imagem não chegou até nosso servidor.",
12248
+ actions: [
12249
+ "Verifique a rede do dispositivo",
12250
+ "Repita a captura em alguns segundos"
12251
+ ]
12252
+ }
12253
+ };
12254
+ function copyForErrorCode(code) {
12255
+ if (!code) return DEFAULT;
12256
+ const key = code.toUpperCase();
12257
+ return MAP[key] ?? DEFAULT;
12258
+ }
12259
+ function detectBrowser(ua = navigator.userAgent) {
12260
+ const s = ua.toLowerCase();
12261
+ if (s.includes("edg/")) return "edge";
12262
+ if (s.includes("firefox")) return "firefox";
12263
+ if (s.includes("chrome") && !s.includes("edg/")) return "chrome";
12264
+ if (s.includes("safari") && !s.includes("chrome")) return "safari";
12265
+ return "unknown";
12266
+ }
12267
+ const CAMERA_INSTRUCTIONS = {
12268
+ chrome: "Clique no cadeado da barra de endereço → Configurações do site → Câmera → Permitir. Recarregue a página.",
12269
+ edge: "Clique no cadeado da barra de endereço → Permissões do site → Câmera → Permitir. Recarregue a página.",
12270
+ firefox: 'Clique no ícone da câmera bloqueada na barra de endereço e escolha "Permitir". Recarregue a página.',
12271
+ safari: "Menu Safari → Configurações → Sites → Câmera → Permitir para este site. Recarregue a página.",
12272
+ unknown: 'Abra as configurações do navegador, procure por "câmera" e permita o acesso para este site. Depois recarregue a página.'
12273
+ };
12274
+ function cameraInstructionFor(browser) {
12275
+ return CAMERA_INSTRUCTIONS[browser];
12276
+ }
12277
+ function classifyMediaError(err) {
12278
+ if (!err || typeof err !== "object") return "UNKNOWN";
12279
+ const name = (err.name ?? "").toString();
12280
+ if (name === "NotAllowedError" || name === "SecurityError") return "PERMISSION_DENIED";
12281
+ if (name === "NotFoundError" || name === "OverconstrainedError") return "NO_CAMERA";
12282
+ if (name === "NotReadableError" || name === "TrackStartError") return "IN_USE";
12283
+ return "UNKNOWN";
12284
+ }
12285
+ const STYLE_ID$1 = "neofaceid-state-views-styles";
12286
+ const CSS = `
12287
+ .neofaceid-root .nfid-state {
12288
+ display: flex; flex-direction: column; align-items: center; text-align: center; gap: 10px;
12289
+ padding: 8px 8px 4px;
12290
+ }
12291
+ .neofaceid-root .nfid-state h2 {
12292
+ margin: 6px 0 0; font-size: 20px; font-weight: 700; color: var(--nfid-text); line-height: 1.25;
12293
+ letter-spacing: -0.015em;
12294
+ }
12295
+ .neofaceid-root .nfid-state p {
12296
+ margin: 0; font-size: 14px; color: var(--nfid-text-muted); line-height: 1.5;
12297
+ }
12298
+ .neofaceid-root .nfid-state-actions {
12299
+ display: flex; flex-direction: column; gap: 10px; width: 100%; margin-top: 12px;
12300
+ }
12301
+ .neofaceid-root .nfid-state-actions--row {
12302
+ flex-direction: row; justify-content: center;
12303
+ }
12304
+ .neofaceid-root .nfid-state-icon-success {
12305
+ width: 64px; height: 64px; color: #0E9F6E;
12306
+ animation: nfidCelebrate 500ms cubic-bezier(.2,1.4,.4,1) both;
12307
+ }
12308
+ @keyframes nfidCelebrate {
12309
+ 0% { transform: scale(0.6); opacity: 0; }
12310
+ 60% { transform: scale(1.08); opacity: 1; }
12311
+ 100% { transform: scale(1); }
12312
+ }
12313
+ .neofaceid-root .nfid-state-warn-icon { width: 56px; height: 56px; color: #F1BE00; }
12314
+ .neofaceid-root .nfid-state-cameraoff-icon { width: 56px; height: 56px; color: var(--nfid-text-muted); }
12315
+ .neofaceid-root .nfid-state-hints {
12316
+ display: flex; flex-direction: column; gap: 6px; margin-top: 8px; text-align: left; width: 100%;
12317
+ }
12318
+ .neofaceid-root .nfid-state-hint {
12319
+ display: flex; gap: 8px; align-items: flex-start; font-size: 13px; color: var(--nfid-text);
12320
+ padding: 8px 12px; background: var(--nfid-surface-muted); border-radius: 12px;
12321
+ }
12322
+ .neofaceid-root .nfid-state-hint::before { content: "•"; color: var(--nfid-action); font-weight: 700; }
12323
+ .neofaceid-root .nfid-state-attempt {
12324
+ font-size: 12px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
12325
+ color: var(--nfid-text-subtle);
12326
+ }
12327
+ .neofaceid-root .nfid-state-techcode {
12328
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
12329
+ font-size: 11px; color: var(--nfid-text-subtle); margin-top: 8px;
12330
+ }
12331
+ `;
12332
+ function useStateStyles() {
12333
+ useEffect(() => {
12334
+ injectThemeStyles$1();
12335
+ injectScopedStyles$1(STYLE_ID$1, CSS);
12336
+ }, []);
12337
+ }
12338
+ function AuthorizeSuccess({ appName, onContinue }) {
12339
+ useStateStyles();
12340
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-state", role: "status", "aria-live": "polite", children: [
12341
+ /* @__PURE__ */ jsxs(
12342
+ "svg",
12343
+ {
12344
+ className: "nfid-state-icon-success",
12345
+ viewBox: "0 0 64 64",
12346
+ fill: "none",
12347
+ stroke: "currentColor",
12348
+ strokeWidth: "4",
12349
+ strokeLinecap: "round",
12350
+ strokeLinejoin: "round",
12351
+ "aria-hidden": "true",
12352
+ children: [
12353
+ /* @__PURE__ */ jsx("circle", { cx: "32", cy: "32", r: "28" }),
12354
+ /* @__PURE__ */ jsx("path", { d: "M20 33 L28 41 L44 24" })
12355
+ ]
12356
+ }
12357
+ ),
12358
+ /* @__PURE__ */ jsx("h2", { children: "Identidade confirmada" }),
12359
+ /* @__PURE__ */ jsxs("p", { children: [
12360
+ "Você já pode continuar no ",
12361
+ appName,
12362
+ "."
12363
+ ] }),
12364
+ /* @__PURE__ */ jsx("div", { className: "nfid-state-actions", children: /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onContinue, children: "Continuar" }) })
12365
+ ] });
12366
+ }
12367
+ function formatHms(d) {
12368
+ const hh = String(d.getHours()).padStart(2, "0");
12369
+ const mm = String(d.getMinutes()).padStart(2, "0");
12370
+ const ss = String(d.getSeconds()).padStart(2, "0");
12371
+ return `${hh}:${mm}:${ss}`;
12372
+ }
12373
+ function AuthorizeFailure({
12374
+ errorCode,
12375
+ attempt,
12376
+ maxAttempts,
12377
+ timestamp,
12378
+ onRetry,
12379
+ onFallback
12380
+ }) {
12381
+ useStateStyles();
12382
+ const copy = copyForErrorCode(errorCode);
12383
+ const stamp = timestamp ?? /* @__PURE__ */ new Date();
12384
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-state", role: "alert", "aria-live": "assertive", children: [
12385
+ /* @__PURE__ */ jsxs(StatusPill, { tone: "attention", children: [
12386
+ "Tentativa ",
12387
+ attempt,
12388
+ " de ",
12389
+ maxAttempts
12390
+ ] }),
12391
+ /* @__PURE__ */ jsxs(
12392
+ "svg",
12393
+ {
12394
+ className: "nfid-state-warn-icon",
12395
+ viewBox: "0 0 64 64",
12396
+ fill: "none",
12397
+ stroke: "currentColor",
12398
+ strokeWidth: "3",
12399
+ strokeLinecap: "round",
12400
+ strokeLinejoin: "round",
12401
+ "aria-hidden": "true",
12402
+ children: [
12403
+ /* @__PURE__ */ jsx("path", { d: "M32 8 L58 54 H6 Z" }),
12404
+ /* @__PURE__ */ jsx("path", { d: "M32 26 V38" }),
12405
+ /* @__PURE__ */ jsx("circle", { cx: "32", cy: "45", r: "2", fill: "currentColor" })
12406
+ ]
12407
+ }
12408
+ ),
12409
+ /* @__PURE__ */ jsx("h2", { children: copy.title }),
12410
+ /* @__PURE__ */ jsx("p", { children: copy.hint }),
12411
+ /* @__PURE__ */ jsxs("div", { className: "nfid-state-hints", "aria-label": "O que fazer", children: [
12412
+ /* @__PURE__ */ jsx("div", { className: "nfid-state-hint", children: copy.actions[0] }),
12413
+ /* @__PURE__ */ jsx("div", { className: "nfid-state-hint", children: copy.actions[1] })
12414
+ ] }),
12415
+ /* @__PURE__ */ jsxs("div", { className: "nfid-state-actions", children: [
12416
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onRetry, children: "Tentar de novo" }),
12417
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onFallback, children: "Verificar de outro jeito" })
12418
+ ] }),
12419
+ /* @__PURE__ */ jsxs("p", { className: "nfid-state-techcode", children: [
12420
+ (errorCode ?? "UNKNOWN").toUpperCase(),
12421
+ " · ",
12422
+ formatHms(stamp)
12423
+ ] })
12424
+ ] });
12425
+ }
12426
+ function AuthorizePermissionDenied({
12427
+ browser,
12428
+ onFallback,
12429
+ onCancel
12430
+ }) {
12431
+ useStateStyles();
12432
+ const detected = browser ?? detectBrowser();
12433
+ const instruction = cameraInstructionFor(detected);
12434
+ return /* @__PURE__ */ jsxs("div", { className: "nfid-state", role: "alert", "aria-live": "assertive", children: [
12435
+ /* @__PURE__ */ jsxs(
12436
+ "svg",
12437
+ {
12438
+ className: "nfid-state-cameraoff-icon",
12439
+ viewBox: "0 0 24 24",
12440
+ fill: "none",
12441
+ stroke: "currentColor",
12442
+ strokeWidth: "1.75",
12443
+ strokeLinecap: "round",
12444
+ strokeLinejoin: "round",
12445
+ "aria-hidden": "true",
12446
+ children: [
12447
+ /* @__PURE__ */ jsx("path", { d: "m3 3 18 18" }),
12448
+ /* @__PURE__ */ jsx("path", { d: "M20.66 15.34A2 2 0 0 0 22 13.5V7.5a2 2 0 0 0-3.13-1.65L15 8.5" }),
12449
+ /* @__PURE__ */ jsx("path", { d: "M9 6H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1" })
12450
+ ]
12451
+ }
12452
+ ),
12453
+ /* @__PURE__ */ jsx("h2", { children: "Câmera bloqueada" }),
12454
+ /* @__PURE__ */ jsx("p", { children: "Precisamos da câmera para confirmar que é você." }),
12455
+ /* @__PURE__ */ jsx("p", { style: { marginTop: 8 }, children: instruction }),
12456
+ /* @__PURE__ */ jsxs("div", { className: "nfid-state-actions", children: [
12457
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: onFallback, children: "Verificar de outro jeito" }),
12458
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onCancel, children: "Agora não" })
12459
+ ] })
12460
+ ] });
12461
+ }
12156
12462
  const STYLE_ID = "neofaceid-authorize-styles";
12157
12463
  const GESTURE_TO_GLYPH = {
12158
12464
  center: "center",
@@ -12186,6 +12492,7 @@ function formatCurrency(amount, locale) {
12186
12492
  return `R$ ${amount.toFixed(2)}`;
12187
12493
  }
12188
12494
  }
12495
+ const MAX_ATTEMPTS = 3;
12189
12496
  function AuthorizeModalComponent({
12190
12497
  applicationToken,
12191
12498
  appName,
@@ -12201,6 +12508,10 @@ function AuthorizeModalComponent({
12201
12508
  const [progress, setProgress] = useState(0);
12202
12509
  const [currentGesture, setCurrentGesture] = useState(null);
12203
12510
  const [phase, setPhase] = useState("preparing");
12511
+ const [attempt, setAttempt] = useState(1);
12512
+ const [failureCode, setFailureCode] = useState(void 0);
12513
+ const [failureAt, setFailureAt] = useState(void 0);
12514
+ const [runCounter, setRunCounter] = useState(0);
12204
12515
  const cancelRef = useRef(false);
12205
12516
  const locale = getLocale();
12206
12517
  const headerAppName = appName ?? getAppName() ?? "sua aplicação";
@@ -12237,11 +12548,27 @@ function AuthorizeModalComponent({
12237
12548
  }, []);
12238
12549
  useEffect(() => {
12239
12550
  let stream = null;
12551
+ cancelRef.current = false;
12552
+ setPhase("preparing");
12553
+ setProgress(0);
12554
+ setCurrentGesture(null);
12240
12555
  async function run() {
12241
12556
  try {
12242
- stream = await navigator.mediaDevices.getUserMedia({
12243
- video: { facingMode: "user", width: { ideal: 1280 }, height: { ideal: 720 } }
12244
- });
12557
+ try {
12558
+ stream = await navigator.mediaDevices.getUserMedia({
12559
+ video: { facingMode: "user", width: { ideal: 1280 }, height: { ideal: 720 } }
12560
+ });
12561
+ } catch (mediaErr) {
12562
+ const kind = classifyMediaError(mediaErr);
12563
+ if (kind === "PERMISSION_DENIED") {
12564
+ setPhase("permission-denied");
12565
+ return;
12566
+ }
12567
+ throw new NeoFaceError(
12568
+ kind === "NO_CAMERA" ? "Câmera não encontrada" : kind === "IN_USE" ? "Câmera em uso por outro app" : "Falha ao acessar a câmera",
12569
+ ErrorType.CAMERA_ERROR
12570
+ );
12571
+ }
12245
12572
  if (videoRef.current) {
12246
12573
  videoRef.current.srcObject = stream;
12247
12574
  videoRef.current.play().catch(() => {
@@ -12262,13 +12589,23 @@ function AuthorizeModalComponent({
12262
12589
  return frame ? [frame] : [];
12263
12590
  }
12264
12591
  });
12265
- setPhase("done");
12592
+ if (stream) stream.getTracks().forEach((t) => t.stop());
12593
+ stream = null;
12594
+ if (result.success === false || result.liveness_passed === false) {
12595
+ setFailureCode("LIVENESS_LOW");
12596
+ setFailureAt(/* @__PURE__ */ new Date());
12597
+ setPhase("failure");
12598
+ return;
12599
+ }
12600
+ setPhase("success");
12266
12601
  if (onSuccess) onSuccess(result);
12267
- onClose();
12268
12602
  } catch (err) {
12603
+ if (cancelRef.current) return;
12269
12604
  const nfe = err instanceof NeoFaceError ? err : new NeoFaceError(err instanceof Error ? err.message : "Falha", ErrorType.UNKNOWN);
12605
+ setFailureCode(nfe.type);
12606
+ setFailureAt(/* @__PURE__ */ new Date());
12607
+ setPhase("failure");
12270
12608
  if (onError) onError(nfe);
12271
- onClose();
12272
12609
  } finally {
12273
12610
  if (stream) stream.getTracks().forEach((t) => t.stop());
12274
12611
  }
@@ -12278,14 +12615,55 @@ function AuthorizeModalComponent({
12278
12615
  cancelRef.current = true;
12279
12616
  if (stream) stream.getTracks().forEach((t) => t.stop());
12280
12617
  };
12281
- }, []);
12618
+ }, [runCounter]);
12282
12619
  const handleClose = () => {
12283
12620
  cancelRef.current = true;
12284
12621
  if (onCancel) onCancel();
12285
12622
  onClose();
12286
12623
  };
12624
+ const handleRetry = () => {
12625
+ if (attempt >= MAX_ATTEMPTS) {
12626
+ handleClose();
12627
+ return;
12628
+ }
12629
+ setAttempt((a) => a + 1);
12630
+ setFailureCode(void 0);
12631
+ setFailureAt(void 0);
12632
+ setRunCounter((c) => c + 1);
12633
+ };
12634
+ const handleFallback = () => {
12635
+ cancelRef.current = true;
12636
+ if (onError) {
12637
+ onError(
12638
+ new NeoFaceError(
12639
+ "Fluxo alternativo solicitado pelo usuário",
12640
+ ErrorType.CONSENT_DENIED
12641
+ )
12642
+ );
12643
+ }
12644
+ onClose();
12645
+ };
12287
12646
  const gestureKey = currentGesture ? GESTURE_TO_GLYPH[currentGesture.gesture_key] ?? "center" : "center";
12288
12647
  const instruction = currentGesture ? GESTURE_TO_INSTRUCTION[currentGesture.gesture_key] ?? "Siga a instrução" : "Preparando a câmera…";
12648
+ if (phase === "success") {
12649
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Identidade confirmada", children: /* @__PURE__ */ jsx(AuthorizeSuccess, { appName: headerAppName, onContinue: onClose }) });
12650
+ }
12651
+ if (phase === "permission-denied") {
12652
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Câmera bloqueada", children: /* @__PURE__ */ jsx(AuthorizePermissionDenied, { onFallback: handleFallback, onCancel: handleClose }) });
12653
+ }
12654
+ if (phase === "failure") {
12655
+ return /* @__PURE__ */ jsx(Sheet, { open: true, dismissOnOverlay: false, ariaLabel: "Não foi possível confirmar", children: /* @__PURE__ */ jsx(
12656
+ AuthorizeFailure,
12657
+ {
12658
+ errorCode: failureCode,
12659
+ attempt,
12660
+ maxAttempts: MAX_ATTEMPTS,
12661
+ timestamp: failureAt,
12662
+ onRetry: handleRetry,
12663
+ onFallback: handleFallback
12664
+ }
12665
+ ) });
12666
+ }
12289
12667
  return /* @__PURE__ */ jsx(Sheet, { open: true, onDismiss: void 0, dismissOnOverlay: false, ariaLabel: "Autorização por biometria", children: /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
12290
12668
  /* @__PURE__ */ jsx(
12291
12669
  "button",
@@ -12557,7 +12935,7 @@ class DocumentCaptureModal {
12557
12935
  </div>
12558
12936
  <div class="neoface-doc-instruction">
12559
12937
  <svg class="neoface-doc-instruction-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
12560
- <span>Evite reflexos e sombras</span>
12938
+ <span>Sem reflexo do flash sobre o texto</span>
12561
12939
  </div>
12562
12940
  </div>
12563
12941
  </div>
@@ -12613,15 +12991,16 @@ class DocumentCaptureModal {
12613
12991
  <div class="neoface-doc-body">
12614
12992
  <div class="neoface-doc-preview-single">
12615
12993
  <span class="neoface-doc-preview-label">${label}</span>
12616
- <img class="neoface-doc-preview-img neoface-doc-preview-current" />
12994
+ <img class="neoface-doc-preview-img neoface-doc-preview-current" alt="Prévia do documento capturado" />
12617
12995
  </div>
12618
-
12996
+
12997
+ <h3 class="neoface-doc-preview-question" id="neoface-doc-preview-question">Está legível?</h3>
12619
12998
  <div class="neoface-doc-preview-hint">
12620
12999
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
12621
- <span>Verifique se todos os dados estão legíveis</span>
13000
+ <span>Confira se dá para ler nome, número e data sem esforço.</span>
12622
13001
  </div>
12623
13002
  </div>
12624
-
13003
+
12625
13004
  <div class="neoface-doc-footer">
12626
13005
  <button class="neoface-doc-btn neoface-doc-btn-secondary neoface-doc-retake-btn">
12627
13006
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -12630,11 +13009,11 @@ class DocumentCaptureModal {
12630
13009
  </svg>
12631
13010
  Refazer
12632
13011
  </button>
12633
- <button class="neoface-doc-btn neoface-doc-btn-success neoface-doc-confirm-btn">
13012
+ <button class="neoface-doc-btn neoface-doc-btn-primary neoface-doc-confirm-btn">
12634
13013
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
12635
13014
  <path d="M20 6L9 17l-5-5"/>
12636
13015
  </svg>
12637
- ${isLastStep ? "Confirmar e Enviar" : "Próximo"}
13016
+ ${isLastStep ? "Sim, enviar" : "Sim, próximo"}
12638
13017
  </button>
12639
13018
  </div>
12640
13019
 
@@ -13051,8 +13430,8 @@ class DocumentCaptureModal {
13051
13430
  }
13052
13431
 
13053
13432
  .neoface-doc-select-option:hover {
13054
- background: rgba(0, 200, 255, 0.1);
13055
- border-color: rgba(0, 200, 255, 0.4);
13433
+ background: rgba(0, 145, 246, 0.1);
13434
+ border-color: rgba(0, 145, 246, 0.4);
13056
13435
  transform: translateX(4px);
13057
13436
  }
13058
13437
 
@@ -13062,7 +13441,7 @@ class DocumentCaptureModal {
13062
13441
  justify-content: center;
13063
13442
  width: 44px;
13064
13443
  height: 44px;
13065
- background: rgba(0, 200, 255, 0.15);
13444
+ background: rgba(0, 145, 246, 0.15);
13066
13445
  border-radius: 12px;
13067
13446
  color: #0091F6;
13068
13447
  }
@@ -13098,8 +13477,8 @@ class DocumentCaptureModal {
13098
13477
  align-items: center;
13099
13478
  justify-content: center;
13100
13479
  padding: 14px 16px;
13101
- background: rgba(0, 200, 255, 0.08);
13102
- border: 1px solid rgba(0, 200, 255, 0.15);
13480
+ background: rgba(0, 145, 246, 0.08);
13481
+ border: 1px solid rgba(0, 145, 246, 0.15);
13103
13482
  border-radius: 10px;
13104
13483
  color: rgba(255, 255, 255, 0.8);
13105
13484
  font-size: 13px;
@@ -13116,13 +13495,13 @@ class DocumentCaptureModal {
13116
13495
  flex-shrink: 0;
13117
13496
  }
13118
13497
 
13119
- /* Camera */
13498
+ /* Camera — ISO/IEC 7810 ID-1 (85.60 × 53.98 mm ≈ 1.586:1) */
13120
13499
  .neoface-doc-camera-container {
13121
13500
  position: relative;
13122
13501
  background: #000;
13123
13502
  border-radius: 16px;
13124
13503
  overflow: hidden;
13125
- aspect-ratio: 4/3;
13504
+ aspect-ratio: 1.586 / 1;
13126
13505
  }
13127
13506
 
13128
13507
  .neoface-doc-video {
@@ -13144,20 +13523,19 @@ class DocumentCaptureModal {
13144
13523
  pointer-events: none;
13145
13524
  }
13146
13525
 
13526
+ /* Moldura Focus Frame — proporção ID-1 (1.586:1), sem borda contínua. */
13147
13527
  .neoface-doc-frame {
13148
- width: 75%;
13149
- height: 65%;
13528
+ width: 82%;
13529
+ aspect-ratio: 1.586 / 1;
13150
13530
  position: relative;
13151
- border: 2px dashed rgba(0, 200, 255, 0.5);
13152
13531
  border-radius: 12px;
13153
- background: rgba(0, 200, 255, 0.03);
13154
13532
  }
13155
-
13533
+
13156
13534
  .neoface-doc-corner {
13157
13535
  position: absolute;
13158
13536
  width: 28px;
13159
13537
  height: 28px;
13160
- border-color: #0091F6;
13538
+ border-color: var(--nfid-action, #0059C4);
13161
13539
  border-style: solid;
13162
13540
  }
13163
13541
 
@@ -13246,23 +13624,31 @@ class DocumentCaptureModal {
13246
13624
  width: 75%;
13247
13625
  height: auto;
13248
13626
  border-radius: 12px;
13249
- border: 2px solid rgba(0, 200, 255, 0.3);
13627
+ border: 2px solid rgba(0, 145, 246, 0.3);
13250
13628
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
13251
13629
  }
13252
13630
 
13631
+ .neoface-doc-preview-question {
13632
+ margin: 12px 0 4px;
13633
+ font-size: 17px;
13634
+ font-weight: 700;
13635
+ color: var(--nfid-text, #0A1320);
13636
+ text-align: center;
13637
+ }
13638
+
13253
13639
  .neoface-doc-preview-hint {
13254
13640
  display: flex;
13255
13641
  align-items: center;
13256
13642
  gap: 10px;
13257
13643
  padding: 12px 16px;
13258
- background: rgba(0, 200, 255, 0.08);
13259
- border: 1px solid rgba(0, 200, 255, 0.15);
13644
+ background: rgba(0, 145, 246, 0.08);
13645
+ border: 1px solid rgba(0, 145, 246, 0.15);
13260
13646
  border-radius: 10px;
13261
13647
  font-size: 13px;
13262
13648
  color: rgba(255, 255, 255, 0.8);
13263
13649
  margin-top: 8px;
13264
13650
  }
13265
-
13651
+
13266
13652
  .neoface-doc-preview-hint svg {
13267
13653
  color: #0091F6;
13268
13654
  flex-shrink: 0;
@@ -13297,7 +13683,7 @@ class DocumentCaptureModal {
13297
13683
 
13298
13684
  .neoface-doc-btn-primary:hover {
13299
13685
  transform: translateY(-2px);
13300
- box-shadow: 0 6px 20px rgba(0, 200, 255, 0.4);
13686
+ box-shadow: 0 6px 20px rgba(0, 145, 246, 0.4);
13301
13687
  }
13302
13688
 
13303
13689
  .neoface-doc-btn-success {