@koraidv/react 1.7.11 → 1.8.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.
package/dist/index.mjs CHANGED
@@ -47,7 +47,8 @@ function useKoraIDV() {
47
47
  currentChallenge: null,
48
48
  completedChallenges: 0,
49
49
  isLoading: false,
50
- error: null
50
+ error: null,
51
+ lastChallengeError: null
51
52
  });
52
53
  const [selectedDocumentType, setSelectedDocumentType] = useState(null);
53
54
  const [documentFrontCaptured, setDocumentFrontCaptured] = useState(false);
@@ -246,14 +247,20 @@ function useKoraIDV() {
246
247
  ...prev,
247
248
  completedChallenges: nextIndex,
248
249
  currentChallenge: nextChallenge,
249
- isLoading: false
250
+ isLoading: false,
251
+ // Clear any prior retake message — the user just succeeded.
252
+ lastChallengeError: null
250
253
  }));
251
254
  if (!nextChallenge) {
252
255
  setState((prev) => ({ ...prev, step: "processing" }));
253
256
  }
254
257
  return true;
255
258
  }
256
- setState((prev) => ({ ...prev, isLoading: false }));
259
+ setState((prev) => ({
260
+ ...prev,
261
+ isLoading: false,
262
+ lastChallengeError: retakeMessageForChallenge(currentChallenge.type)
263
+ }));
257
264
  return false;
258
265
  } catch (error) {
259
266
  setState((prev) => ({
@@ -304,7 +311,8 @@ function useKoraIDV() {
304
311
  currentChallenge: null,
305
312
  completedChallenges: 0,
306
313
  isLoading: false,
307
- error: null
314
+ error: null,
315
+ lastChallengeError: null
308
316
  });
309
317
  }, [sdk]);
310
318
  const retry = useCallback(() => {
@@ -331,9 +339,27 @@ function useKoraIDV() {
331
339
  sdk
332
340
  };
333
341
  }
342
+ function retakeMessageForChallenge(type) {
343
+ switch (type) {
344
+ case "blink":
345
+ return "We didn't catch the blink \u2014 close both eyes briefly and try again.";
346
+ case "smile":
347
+ return "We didn't catch the smile \u2014 show your teeth and try again.";
348
+ case "turn_left":
349
+ return "Turn your head a bit further to the left and try again.";
350
+ case "turn_right":
351
+ return "Turn your head a bit further to the right and try again.";
352
+ case "nod_up":
353
+ return "Tilt your head a bit higher and try again.";
354
+ case "nod_down":
355
+ return "Tilt your head a bit lower and try again.";
356
+ default:
357
+ return "That attempt didn't pass \u2014 follow the prompt and try again.";
358
+ }
359
+ }
334
360
 
335
361
  // src/components/VerificationFlow.tsx
336
- import { useEffect as useEffect8, useState as useState6 } from "react";
362
+ import { useEffect as useEffect11, useState as useState9 } from "react";
337
363
  import { KoraError as KoraError2, KoraErrorCode } from "@koraidv/core";
338
364
 
339
365
  // src/components/styles.ts
@@ -400,6 +426,36 @@ function injectKeyframes() {
400
426
  from { opacity: 0; transform: translateY(8px); }
401
427
  to { opacity: 1; transform: translateY(0); }
402
428
  }
429
+ /* \u2500\u2500\u2500 VisualGuides motion (v1.8.0) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
430
+ @keyframes kora-head-turn-right {
431
+ 0%, 100% { transform: rotate(0deg); }
432
+ 50% { transform: rotate(22deg); }
433
+ }
434
+ @keyframes kora-head-turn-left {
435
+ 0%, 100% { transform: rotate(0deg); }
436
+ 50% { transform: rotate(-22deg); }
437
+ }
438
+ @keyframes kora-head-tilt-up {
439
+ 0%, 100% { transform: translateY(0); }
440
+ 50% { transform: translateY(-6px) rotate(-6deg); }
441
+ }
442
+ @keyframes kora-head-tilt-down {
443
+ 0%, 100% { transform: translateY(0); }
444
+ 50% { transform: translateY(6px) rotate(6deg); }
445
+ }
446
+ @keyframes kora-smile {
447
+ 0%, 100% { transform: scaleY(0.5); }
448
+ 50% { transform: scaleY(1.2); }
449
+ }
450
+ @keyframes kora-blink {
451
+ 0%, 80%, 100% { transform: scaleY(1); }
452
+ 88% { transform: scaleY(0.05); }
453
+ }
454
+ @keyframes kora-nfc-wave {
455
+ 0% { opacity: 0; transform: translateX(-4px); }
456
+ 40% { opacity: 1; }
457
+ 100% { opacity: 0; transform: translateX(6px); }
458
+ }
403
459
  `;
404
460
  document.head.appendChild(style);
405
461
  }
@@ -1313,7 +1369,7 @@ var styles = {
1313
1369
  };
1314
1370
 
1315
1371
  // src/components/DesignSystem.tsx
1316
- import { useEffect as useEffect2 } from "react";
1372
+ import { useEffect as useEffect2, useState as useState2 } from "react";
1317
1373
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
1318
1374
  function StepProgressBar({ total, current, isDark = false }) {
1319
1375
  return /* @__PURE__ */ jsx2("div", { style: styles.progressBar, children: Array.from({ length: total }).map((_, i) => /* @__PURE__ */ jsx2(
@@ -1386,10 +1442,26 @@ function ScoreMetricRow({ label, score, icon, status, message }) {
1386
1442
  }
1387
1443
  );
1388
1444
  }
1389
- function ProcessingScreen({ steps }) {
1445
+ var DEFAULT_AUTO_STEPS = [
1446
+ "Document analyzed",
1447
+ "Checking face match",
1448
+ "Finalizing results"
1449
+ ];
1450
+ function ProcessingScreen({ steps, autoAdvance = true }) {
1390
1451
  useEffect2(() => {
1391
1452
  injectKeyframes();
1392
1453
  }, []);
1454
+ const [autoIndex, setAutoIndex] = useState2(0);
1455
+ useEffect2(() => {
1456
+ if (steps || !autoAdvance) return;
1457
+ if (autoIndex >= DEFAULT_AUTO_STEPS.length - 1) return;
1458
+ const t = setTimeout(() => setAutoIndex((i) => i + 1), 1400);
1459
+ return () => clearTimeout(t);
1460
+ }, [autoIndex, steps, autoAdvance]);
1461
+ const renderedSteps = steps ? steps : DEFAULT_AUTO_STEPS.map((label, i) => ({
1462
+ label,
1463
+ status: i < autoIndex ? "done" : i === autoIndex ? "active" : "pending"
1464
+ }));
1393
1465
  return /* @__PURE__ */ jsxs("div", { style: styles.processingContainer, children: [
1394
1466
  /* @__PURE__ */ jsxs("div", { style: styles.spinnerContainer, children: [
1395
1467
  /* @__PURE__ */ jsx2(
@@ -1440,7 +1512,7 @@ function ProcessingScreen({ steps }) {
1440
1512
  }
1441
1513
  )
1442
1514
  ] }),
1443
- /* @__PURE__ */ jsx2("div", { style: styles.processingSteps, children: steps.map((step, i) => /* @__PURE__ */ jsxs("div", { style: styles.processingStep, children: [
1515
+ /* @__PURE__ */ jsx2("div", { style: styles.processingSteps, children: renderedSteps.map((step, i) => /* @__PURE__ */ jsxs("div", { style: styles.processingStep, children: [
1444
1516
  /* @__PURE__ */ jsx2(
1445
1517
  "div",
1446
1518
  {
@@ -1465,6 +1537,7 @@ function ProcessingScreen({ steps }) {
1465
1537
  ] });
1466
1538
  }
1467
1539
  function computeScoreBreakdown(verification) {
1540
+ const source = verification.metadata?.source ?? "";
1468
1541
  const livenessPercent = Math.round(
1469
1542
  verification.scores?.liveness ?? verification.livenessVerification?.livenessScore ?? 0
1470
1543
  );
@@ -1477,9 +1550,12 @@ function computeScoreBreakdown(verification) {
1477
1550
  const selfiePercent = Math.round(
1478
1551
  verification.scores?.faceMatch ?? verification.faceVerification?.matchScore ?? 0
1479
1552
  );
1553
+ const isWeb = source === "web";
1554
+ const passFloor = isWeb ? 65 : 75;
1555
+ const borderlineFloor = isWeb ? 40 : 50;
1480
1556
  function getStatus(score) {
1481
- if (score >= 75) return "pass";
1482
- if (score >= 50) return "borderline";
1557
+ if (score >= passFloor) return "pass";
1558
+ if (score >= borderlineFloor) return "borderline";
1483
1559
  return "fail";
1484
1560
  }
1485
1561
  function getMessage(status) {
@@ -1608,11 +1684,11 @@ function ConsentItem({
1608
1684
  }
1609
1685
 
1610
1686
  // src/components/CountrySelectionScreen.tsx
1611
- import { useState as useState2, useMemo as useMemo2 } from "react";
1687
+ import { useState as useState3, useMemo as useMemo2 } from "react";
1612
1688
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1613
1689
  function CountrySelectionScreen({ countries, onSelect, onCancel }) {
1614
- const [selected, setSelected] = useState2(null);
1615
- const [searchQuery, setSearchQuery] = useState2("");
1690
+ const [selected, setSelected] = useState3(null);
1691
+ const [searchQuery, setSearchQuery] = useState3("");
1616
1692
  const filteredCountries = useMemo2(() => {
1617
1693
  const countryList = countries || [];
1618
1694
  if (!searchQuery.trim()) return countryList;
@@ -1762,8 +1838,243 @@ function getIcon(type) {
1762
1838
  }
1763
1839
 
1764
1840
  // src/components/DocumentCaptureScreen.tsx
1765
- import { useRef as useRef2, useEffect as useEffect3, useState as useState3, useCallback as useCallback2 } from "react";
1766
- import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1841
+ import { useRef as useRef3, useEffect as useEffect5, useState as useState5, useCallback as useCallback2 } from "react";
1842
+
1843
+ // src/components/VisualGuides.tsx
1844
+ import { useEffect as useEffect3 } from "react";
1845
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1846
+ function visualGuideForChallenge(challengeType) {
1847
+ switch (challengeType) {
1848
+ case "turn_left":
1849
+ return "livenessTurnLeft";
1850
+ case "turn_right":
1851
+ return "livenessTurnRight";
1852
+ case "nod_up":
1853
+ return "livenessLookUp";
1854
+ case "nod_down":
1855
+ return "livenessLookDown";
1856
+ case "smile":
1857
+ return "livenessSmile";
1858
+ case "blink":
1859
+ return "livenessBlink";
1860
+ default:
1861
+ return null;
1862
+ }
1863
+ }
1864
+ function VisualGuide({ kind, size = 96 }) {
1865
+ useEffect3(() => {
1866
+ injectKeyframes();
1867
+ }, []);
1868
+ const common = { width: size, height: size, viewBox: "0 0 100 100" };
1869
+ const fg = colors.teal;
1870
+ const dim = "rgba(255,255,255,0.3)";
1871
+ switch (kind) {
1872
+ case "docFront":
1873
+ return /* @__PURE__ */ jsx6(DocFront, { ...common, fg, dim });
1874
+ case "docBack":
1875
+ return /* @__PURE__ */ jsx6(DocBack, { ...common, fg, dim });
1876
+ case "selfie":
1877
+ return /* @__PURE__ */ jsx6(Selfie, { ...common, fg, dim });
1878
+ case "nfcScan":
1879
+ return /* @__PURE__ */ jsx6(NfcScan, { ...common, fg, dim });
1880
+ case "livenessTurnLeft":
1881
+ return /* @__PURE__ */ jsx6(HeadTurn, { ...common, fg, dim, right: false });
1882
+ case "livenessTurnRight":
1883
+ return /* @__PURE__ */ jsx6(HeadTurn, { ...common, fg, dim, right: true });
1884
+ case "livenessLookUp":
1885
+ return /* @__PURE__ */ jsx6(HeadTilt, { ...common, fg, dim, up: true });
1886
+ case "livenessLookDown":
1887
+ return /* @__PURE__ */ jsx6(HeadTilt, { ...common, fg, dim, up: false });
1888
+ case "livenessSmile":
1889
+ return /* @__PURE__ */ jsx6(Smile, { ...common, fg, dim });
1890
+ case "livenessBlink":
1891
+ return /* @__PURE__ */ jsx6(Blink, { ...common, fg, dim });
1892
+ }
1893
+ }
1894
+ function DocFront({ width, height, viewBox, fg, dim }) {
1895
+ return /* @__PURE__ */ jsxs5("svg", { width, height, viewBox, fill: "none", children: [
1896
+ /* @__PURE__ */ jsx6("rect", { x: "8", y: "26", width: "84", height: "48", rx: "5", stroke: fg, strokeWidth: "2.5" }),
1897
+ /* @__PURE__ */ jsx6("rect", { x: "14", y: "34", width: "22", height: "28", rx: "2", fill: dim }),
1898
+ /* @__PURE__ */ jsx6("rect", { x: "42", y: "36", width: "44", height: "3", rx: "1.5", fill: fg }),
1899
+ /* @__PURE__ */ jsx6("rect", { x: "42", y: "44", width: "36", height: "2.5", rx: "1.25", fill: dim }),
1900
+ /* @__PURE__ */ jsx6("rect", { x: "42", y: "50", width: "40", height: "2.5", rx: "1.25", fill: dim }),
1901
+ /* @__PURE__ */ jsx6("rect", { x: "42", y: "56", width: "30", height: "2.5", rx: "1.25", fill: dim })
1902
+ ] });
1903
+ }
1904
+ function DocBack({ width, height, viewBox, fg, dim }) {
1905
+ return /* @__PURE__ */ jsxs5("svg", { width, height, viewBox, fill: "none", children: [
1906
+ /* @__PURE__ */ jsx6("rect", { x: "8", y: "26", width: "84", height: "48", rx: "5", stroke: fg, strokeWidth: "2.5" }),
1907
+ [16, 19, 22, 26, 28, 32, 35, 39, 42, 46, 49, 53, 56, 60].map((x, i) => /* @__PURE__ */ jsx6(
1908
+ "rect",
1909
+ {
1910
+ x,
1911
+ y: "34",
1912
+ width: i % 3 === 0 ? 2 : 1.2,
1913
+ height: "20",
1914
+ fill: fg
1915
+ },
1916
+ i
1917
+ )),
1918
+ /* @__PURE__ */ jsx6("line", { x1: "14", y1: "64", x2: "58", y2: "64", stroke: dim, strokeWidth: "1.5" }),
1919
+ /* @__PURE__ */ jsx6("rect", { x: "66", y: "34", width: "20", height: "20", rx: "1", fill: dim })
1920
+ ] });
1921
+ }
1922
+ function Selfie({ width, height, viewBox, fg, dim }) {
1923
+ return /* @__PURE__ */ jsxs5("svg", { width, height, viewBox, fill: "none", children: [
1924
+ /* @__PURE__ */ jsx6("ellipse", { cx: "50", cy: "42", rx: "22", ry: "28", stroke: fg, strokeWidth: "2.5" }),
1925
+ /* @__PURE__ */ jsx6("circle", { cx: "42", cy: "38", r: "2.5", fill: fg }),
1926
+ /* @__PURE__ */ jsx6("circle", { cx: "58", cy: "38", r: "2.5", fill: fg }),
1927
+ /* @__PURE__ */ jsx6("path", { d: "M 40 50 Q 50 56 60 50", stroke: fg, strokeWidth: "2", strokeLinecap: "round", fill: "none" }),
1928
+ /* @__PURE__ */ jsx6("path", { d: "M 18 92 Q 18 76 36 70 L 64 70 Q 82 76 82 92", stroke: dim, strokeWidth: "2", fill: "none" })
1929
+ ] });
1930
+ }
1931
+ function NfcScan({ width, height, viewBox, fg, dim }) {
1932
+ return /* @__PURE__ */ jsxs5("svg", { width, height, viewBox, fill: "none", children: [
1933
+ /* @__PURE__ */ jsx6("rect", { x: "14", y: "40", width: "40", height: "50", rx: "3", stroke: dim, strokeWidth: "2" }),
1934
+ /* @__PURE__ */ jsx6("circle", { cx: "34", cy: "60", r: "6", stroke: dim, strokeWidth: "1.5" }),
1935
+ /* @__PURE__ */ jsx6("rect", { x: "62", y: "22", width: "26", height: "52", rx: "4", stroke: fg, strokeWidth: "2.5" }),
1936
+ /* @__PURE__ */ jsx6("rect", { x: "66", y: "26", width: "18", height: "38", rx: "1.5", fill: dim, opacity: "0.5" }),
1937
+ /* @__PURE__ */ jsx6("path", { d: "M 58 48 Q 52 48 50 56", stroke: fg, strokeWidth: "2", fill: "none", style: { animation: "kora-nfc-wave 1.6s ease-out infinite" } }),
1938
+ /* @__PURE__ */ jsx6("path", { d: "M 58 44 Q 50 44 46 56", stroke: fg, strokeWidth: "2", fill: "none", opacity: "0.7", style: { animation: "kora-nfc-wave 1.6s ease-out infinite 0.3s" } })
1939
+ ] });
1940
+ }
1941
+ function HeadTurn({ width, height, viewBox, fg, dim, right }) {
1942
+ const arrowPath = right ? "M 30 12 L 70 12 L 64 6 M 70 12 L 64 18" : "M 70 12 L 30 12 L 36 6 M 30 12 L 36 18";
1943
+ return /* @__PURE__ */ jsxs5("svg", { width, height, viewBox, fill: "none", children: [
1944
+ /* @__PURE__ */ jsx6("path", { d: arrowPath, stroke: fg, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none" }),
1945
+ /* @__PURE__ */ jsxs5(
1946
+ "g",
1947
+ {
1948
+ style: {
1949
+ transformOrigin: "50px 56px",
1950
+ animation: right ? "kora-head-turn-right 2s ease-in-out infinite" : "kora-head-turn-left 2s ease-in-out infinite"
1951
+ },
1952
+ children: [
1953
+ /* @__PURE__ */ jsx6("ellipse", { cx: "50", cy: "55", rx: "20", ry: "26", stroke: fg, strokeWidth: "2.5" }),
1954
+ /* @__PURE__ */ jsx6("circle", { cx: "42", cy: "50", r: "2", fill: fg }),
1955
+ /* @__PURE__ */ jsx6("circle", { cx: "58", cy: "50", r: "2", fill: fg }),
1956
+ /* @__PURE__ */ jsx6("path", { d: "M 50 54 L 50 62", stroke: dim, strokeWidth: "1.5", strokeLinecap: "round" })
1957
+ ]
1958
+ }
1959
+ )
1960
+ ] });
1961
+ }
1962
+ function HeadTilt({ width, height, viewBox, fg, dim, up }) {
1963
+ const arrowPath = up ? "M 50 92 L 50 14 M 44 22 L 50 14 L 56 22" : "M 50 14 L 50 92 M 44 84 L 50 92 L 56 84";
1964
+ return /* @__PURE__ */ jsxs5("svg", { width, height, viewBox, fill: "none", children: [
1965
+ /* @__PURE__ */ jsx6("path", { d: arrowPath, stroke: fg, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", opacity: "0.4", fill: "none" }),
1966
+ /* @__PURE__ */ jsxs5(
1967
+ "g",
1968
+ {
1969
+ style: {
1970
+ transformOrigin: "50px 56px",
1971
+ animation: up ? "kora-head-tilt-up 2s ease-in-out infinite" : "kora-head-tilt-down 2s ease-in-out infinite"
1972
+ },
1973
+ children: [
1974
+ /* @__PURE__ */ jsx6("ellipse", { cx: "50", cy: "55", rx: "20", ry: "26", stroke: fg, strokeWidth: "2.5" }),
1975
+ /* @__PURE__ */ jsx6("circle", { cx: "42", cy: "50", r: "2", fill: fg }),
1976
+ /* @__PURE__ */ jsx6("circle", { cx: "58", cy: "50", r: "2", fill: fg }),
1977
+ /* @__PURE__ */ jsx6("path", { d: "M 50 54 L 50 62", stroke: dim, strokeWidth: "1.5", strokeLinecap: "round" })
1978
+ ]
1979
+ }
1980
+ )
1981
+ ] });
1982
+ }
1983
+ function Smile({ width, height, viewBox, fg, dim }) {
1984
+ return /* @__PURE__ */ jsxs5("svg", { width, height, viewBox, fill: "none", children: [
1985
+ /* @__PURE__ */ jsx6("ellipse", { cx: "50", cy: "50", rx: "24", ry: "30", stroke: fg, strokeWidth: "2.5" }),
1986
+ /* @__PURE__ */ jsx6("circle", { cx: "40", cy: "44", r: "2.5", fill: fg }),
1987
+ /* @__PURE__ */ jsx6("circle", { cx: "60", cy: "44", r: "2.5", fill: fg }),
1988
+ /* @__PURE__ */ jsx6(
1989
+ "path",
1990
+ {
1991
+ d: "M 38 60 Q 50 68 62 60",
1992
+ stroke: fg,
1993
+ strokeWidth: "2.5",
1994
+ strokeLinecap: "round",
1995
+ fill: "none",
1996
+ style: { animation: "kora-smile 2s ease-in-out infinite", transformOrigin: "50px 60px" }
1997
+ }
1998
+ ),
1999
+ /* @__PURE__ */ jsx6("line", { x1: "40", y1: "60", x2: "60", y2: "60", stroke: dim, strokeWidth: "1.5", opacity: "0.3" })
2000
+ ] });
2001
+ }
2002
+ function Blink({ width, height, viewBox, fg, dim }) {
2003
+ return /* @__PURE__ */ jsxs5("svg", { width, height, viewBox, fill: "none", children: [
2004
+ /* @__PURE__ */ jsx6("ellipse", { cx: "50", cy: "50", rx: "24", ry: "30", stroke: fg, strokeWidth: "2.5" }),
2005
+ /* @__PURE__ */ jsxs5("g", { style: { animation: "kora-blink 1.6s ease-in-out infinite" }, children: [
2006
+ /* @__PURE__ */ jsx6("circle", { cx: "40", cy: "44", r: "3", fill: fg }),
2007
+ /* @__PURE__ */ jsx6("circle", { cx: "60", cy: "44", r: "3", fill: fg })
2008
+ ] }),
2009
+ /* @__PURE__ */ jsx6("path", { d: "M 42 60 Q 50 64 58 60", stroke: dim, strokeWidth: "2", strokeLinecap: "round", fill: "none" })
2010
+ ] });
2011
+ }
2012
+
2013
+ // src/hooks/useDocumentDetection.ts
2014
+ import { useEffect as useEffect4, useRef as useRef2, useState as useState4 } from "react";
2015
+ var FRAME_INTERVAL_MS = 300;
2016
+ function useDocumentDetection(videoRef, side) {
2017
+ const [signals, setSignals] = useState4({
2018
+ documentDetected: false,
2019
+ detectorActive: false
2020
+ });
2021
+ const detectorRef = useRef2(null);
2022
+ const intervalRef = useRef2(null);
2023
+ useEffect4(() => {
2024
+ let cancelled = false;
2025
+ async function setup() {
2026
+ const win = typeof window !== "undefined" ? window : null;
2027
+ if (!win) return;
2028
+ let DetectorCtor = null;
2029
+ if (side === "front" && "FaceDetector" in win) {
2030
+ DetectorCtor = win["FaceDetector"];
2031
+ } else if (side === "back" && "BarcodeDetector" in win) {
2032
+ DetectorCtor = win["BarcodeDetector"];
2033
+ }
2034
+ if (!DetectorCtor) {
2035
+ return;
2036
+ }
2037
+ try {
2038
+ const detector = new DetectorCtor(
2039
+ side === "front" ? { fastMode: true, maxDetectedFaces: 1 } : { formats: ["pdf417", "qr_code", "data_matrix", "code_128"] }
2040
+ );
2041
+ if (cancelled) return;
2042
+ detectorRef.current = detector;
2043
+ setSignals((prev) => ({ ...prev, detectorActive: true }));
2044
+ } catch {
2045
+ return;
2046
+ }
2047
+ intervalRef.current = setInterval(async () => {
2048
+ const detector = detectorRef.current;
2049
+ const video = videoRef.current;
2050
+ if (!detector || !video || video.readyState < 2) return;
2051
+ try {
2052
+ const results = await detector.detect(video);
2053
+ if (cancelled) return;
2054
+ setSignals((prev) => {
2055
+ const next = results.length > 0;
2056
+ if (prev.documentDetected === next) return prev;
2057
+ return { ...prev, documentDetected: next };
2058
+ });
2059
+ } catch {
2060
+ }
2061
+ }, FRAME_INTERVAL_MS);
2062
+ }
2063
+ setup();
2064
+ return () => {
2065
+ cancelled = true;
2066
+ if (intervalRef.current) {
2067
+ clearInterval(intervalRef.current);
2068
+ intervalRef.current = null;
2069
+ }
2070
+ detectorRef.current = null;
2071
+ };
2072
+ }, [videoRef, side]);
2073
+ return signals;
2074
+ }
2075
+
2076
+ // src/components/DocumentCaptureScreen.tsx
2077
+ import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1767
2078
  var qualityIssueMessages = {
1768
2079
  face_blurred: "Photo on document is blurry. Retake in better lighting.",
1769
2080
  low_resolution: "Image quality too low. Move closer to document.",
@@ -1780,23 +2091,25 @@ function DocumentCaptureScreen({
1780
2091
  requiresBack = true,
1781
2092
  onQualityCheck,
1782
2093
  onCapture,
1783
- onCancel
2094
+ onCancel,
2095
+ showVisualGuides = true
1784
2096
  }) {
1785
- const videoRef = useRef2(null);
1786
- const canvasRef = useRef2(null);
1787
- const guideRef = useRef2(null);
1788
- const [stream, setStream] = useState3(null);
1789
- const [isCapturing, setIsCapturing] = useState3(false);
1790
- const [error, setError] = useState3(null);
1791
- const [capturedImage, setCapturedImage] = useState3(null);
1792
- const [capturedBlob, setCapturedBlob] = useState3(null);
1793
- const [qualityResult, setQualityResult] = useState3(null);
1794
- const [isCheckingQuality, setIsCheckingQuality] = useState3(false);
1795
- const [retakeCount, setRetakeCount] = useState3(0);
1796
- useEffect3(() => {
2097
+ const videoRef = useRef3(null);
2098
+ const canvasRef = useRef3(null);
2099
+ const guideRef = useRef3(null);
2100
+ const [stream, setStream] = useState5(null);
2101
+ const documentSignals = useDocumentDetection(videoRef, side);
2102
+ const [isCapturing, setIsCapturing] = useState5(false);
2103
+ const [error, setError] = useState5(null);
2104
+ const [capturedImage, setCapturedImage] = useState5(null);
2105
+ const [capturedBlob, setCapturedBlob] = useState5(null);
2106
+ const [qualityResult, setQualityResult] = useState5(null);
2107
+ const [isCheckingQuality, setIsCheckingQuality] = useState5(false);
2108
+ const [retakeCount, setRetakeCount] = useState5(0);
2109
+ useEffect5(() => {
1797
2110
  injectKeyframes();
1798
2111
  }, []);
1799
- useEffect3(() => {
2112
+ useEffect5(() => {
1800
2113
  let mounted = true;
1801
2114
  async function startCamera() {
1802
2115
  try {
@@ -1818,7 +2131,7 @@ function DocumentCaptureScreen({
1818
2131
  mounted = false;
1819
2132
  };
1820
2133
  }, [capturedImage]);
1821
- useEffect3(() => {
2134
+ useEffect5(() => {
1822
2135
  return () => {
1823
2136
  stream?.getTracks().forEach((t) => t.stop());
1824
2137
  };
@@ -1911,24 +2224,24 @@ function DocumentCaptureScreen({
1911
2224
  }
1912
2225
  };
1913
2226
  if (error) {
1914
- return /* @__PURE__ */ jsx6("div", { style: styles.container, children: /* @__PURE__ */ jsxs5("div", { style: styles.errorContainer, children: [
1915
- /* @__PURE__ */ jsx6("p", { style: styles.errorText, children: error }),
1916
- /* @__PURE__ */ jsx6("button", { style: styles.primaryButton, onClick: onCancel, children: "Go Back" })
2227
+ return /* @__PURE__ */ jsx7("div", { style: styles.container, children: /* @__PURE__ */ jsxs6("div", { style: styles.errorContainer, children: [
2228
+ /* @__PURE__ */ jsx7("p", { style: styles.errorText, children: error }),
2229
+ /* @__PURE__ */ jsx7("button", { style: styles.primaryButton, onClick: onCancel, children: "Go Back" })
1917
2230
  ] }) });
1918
2231
  }
1919
2232
  if (capturedImage) {
1920
2233
  const qualityPassed = qualityResult && qualityResult.qualityScore >= 60;
1921
2234
  const qualityFailed = qualityResult && qualityResult.qualityScore < 60;
1922
2235
  const canContinueAnyway = qualityFailed && retakeCount >= 2;
1923
- return /* @__PURE__ */ jsxs5("div", { style: styles.darkContainer, children: [
1924
- /* @__PURE__ */ jsx6(StepProgressBar, { total: 5, current: 3, isDark: true }),
1925
- /* @__PURE__ */ jsxs5("div", { style: styles.darkScreenHeader, children: [
1926
- /* @__PURE__ */ jsx6("div", { style: { width: 40 } }),
1927
- /* @__PURE__ */ jsx6("h1", { style: styles.darkScreenTitle, children: "Review your photo" }),
1928
- /* @__PURE__ */ jsx6("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2236
+ return /* @__PURE__ */ jsxs6("div", { style: styles.darkContainer, children: [
2237
+ /* @__PURE__ */ jsx7(StepProgressBar, { total: 5, current: 3, isDark: true }),
2238
+ /* @__PURE__ */ jsxs6("div", { style: styles.darkScreenHeader, children: [
2239
+ /* @__PURE__ */ jsx7("div", { style: { width: 40 } }),
2240
+ /* @__PURE__ */ jsx7("h1", { style: styles.darkScreenTitle, children: "Review your photo" }),
2241
+ /* @__PURE__ */ jsx7("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
1929
2242
  ] }),
1930
- /* @__PURE__ */ jsx6("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "24px" }, children: /* @__PURE__ */ jsxs5("div", { style: styles.reviewCard, children: [
1931
- /* @__PURE__ */ jsx6(
2243
+ /* @__PURE__ */ jsx7("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "24px" }, children: /* @__PURE__ */ jsxs6("div", { style: styles.reviewCard, children: [
2244
+ /* @__PURE__ */ jsx7(
1932
2245
  "img",
1933
2246
  {
1934
2247
  src: capturedImage,
@@ -1936,42 +2249,42 @@ function DocumentCaptureScreen({
1936
2249
  style: { width: "100%", maxWidth: "300px", borderRadius: "16px", display: "block", margin: "0 auto" }
1937
2250
  }
1938
2251
  ),
1939
- isCheckingQuality && /* @__PURE__ */ jsx6("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsx6("span", { style: { ...styles.reviewBadge, backgroundColor: "rgba(255,255,255,0.1)" }, children: "Checking quality..." }) }),
1940
- qualityPassed && /* @__PURE__ */ jsxs5(Fragment, { children: [
1941
- /* @__PURE__ */ jsx6("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsxs5("span", { style: styles.reviewBadge, children: [
2252
+ isCheckingQuality && /* @__PURE__ */ jsx7("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsx7("span", { style: { ...styles.reviewBadge, backgroundColor: "rgba(255,255,255,0.1)" }, children: "Checking quality..." }) }),
2253
+ qualityPassed && /* @__PURE__ */ jsxs6(Fragment, { children: [
2254
+ /* @__PURE__ */ jsx7("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsxs6("span", { style: styles.reviewBadge, children: [
1942
2255
  "\u2713 Quality score: ",
1943
2256
  Math.round(qualityResult.qualityScore),
1944
2257
  "%"
1945
2258
  ] }) }),
1946
- /* @__PURE__ */ jsxs5("div", { style: styles.qualityChecks, children: [
1947
- /* @__PURE__ */ jsx6(QualityCheck, { label: "Sharp" }),
1948
- /* @__PURE__ */ jsx6(QualityCheck, { label: "Well-lit" }),
1949
- /* @__PURE__ */ jsx6(QualityCheck, { label: "Readable" })
2259
+ /* @__PURE__ */ jsxs6("div", { style: styles.qualityChecks, children: [
2260
+ /* @__PURE__ */ jsx7(QualityCheck, { label: "Sharp" }),
2261
+ /* @__PURE__ */ jsx7(QualityCheck, { label: "Well-lit" }),
2262
+ /* @__PURE__ */ jsx7(QualityCheck, { label: "Readable" })
1950
2263
  ] })
1951
2264
  ] }),
1952
- qualityFailed && /* @__PURE__ */ jsxs5(Fragment, { children: [
1953
- /* @__PURE__ */ jsx6("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsxs5("span", { style: { ...styles.reviewBadge, backgroundColor: "rgba(239,68,68,0.15)", color: "#ef4444" }, children: [
2265
+ qualityFailed && /* @__PURE__ */ jsxs6(Fragment, { children: [
2266
+ /* @__PURE__ */ jsx7("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsxs6("span", { style: { ...styles.reviewBadge, backgroundColor: "rgba(239,68,68,0.15)", color: "#ef4444" }, children: [
1954
2267
  "\u26A0 Quality score: ",
1955
2268
  Math.round(qualityResult.qualityScore),
1956
2269
  "%"
1957
2270
  ] }) }),
1958
- /* @__PURE__ */ jsx6("div", { style: { padding: "12px 0" }, children: qualityResult.qualityIssues.map((issue, i) => /* @__PURE__ */ jsx6("p", { style: { color: "rgba(255,255,255,0.7)", fontSize: "13px", margin: "4px 0", textAlign: "center" }, children: qualityIssueMessages[issue] || issue }, i)) })
2271
+ /* @__PURE__ */ jsx7("div", { style: { padding: "12px 0" }, children: qualityResult.qualityIssues.map((issue, i) => /* @__PURE__ */ jsx7("p", { style: { color: "rgba(255,255,255,0.7)", fontSize: "13px", margin: "4px 0", textAlign: "center" }, children: qualityIssueMessages[issue] || issue }, i)) })
1959
2272
  ] }),
1960
- !qualityResult && !isCheckingQuality && /* @__PURE__ */ jsxs5(Fragment, { children: [
1961
- /* @__PURE__ */ jsx6("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsx6("span", { style: styles.reviewBadge, children: "\u2713 Good quality" }) }),
1962
- /* @__PURE__ */ jsxs5("div", { style: styles.qualityChecks, children: [
1963
- /* @__PURE__ */ jsx6(QualityCheck, { label: "Sharp" }),
1964
- /* @__PURE__ */ jsx6(QualityCheck, { label: "Well-lit" }),
1965
- /* @__PURE__ */ jsx6(QualityCheck, { label: "No glare" })
2273
+ !qualityResult && !isCheckingQuality && /* @__PURE__ */ jsxs6(Fragment, { children: [
2274
+ /* @__PURE__ */ jsx7("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsx7("span", { style: styles.reviewBadge, children: "\u2713 Good quality" }) }),
2275
+ /* @__PURE__ */ jsxs6("div", { style: styles.qualityChecks, children: [
2276
+ /* @__PURE__ */ jsx7(QualityCheck, { label: "Sharp" }),
2277
+ /* @__PURE__ */ jsx7(QualityCheck, { label: "Well-lit" }),
2278
+ /* @__PURE__ */ jsx7(QualityCheck, { label: "No glare" })
1966
2279
  ] })
1967
2280
  ] })
1968
2281
  ] }) }),
1969
- /* @__PURE__ */ jsx6("div", { style: styles.reviewButtonsRow, children: qualityFailed ? /* @__PURE__ */ jsxs5(Fragment, { children: [
1970
- /* @__PURE__ */ jsx6("button", { style: { ...styles.darkOutlineButton, flex: 1 }, onClick: handleRetake, children: "Retake" }),
1971
- canContinueAnyway && /* @__PURE__ */ jsx6("button", { style: { ...styles.primaryButton, flex: 1 }, onClick: handleContinueAnyway, children: "Continue anyway" })
1972
- ] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
1973
- /* @__PURE__ */ jsx6("button", { style: { ...styles.darkOutlineButton, flex: 1 }, onClick: handleRetake, children: "Retake" }),
1974
- /* @__PURE__ */ jsx6(
2282
+ /* @__PURE__ */ jsx7("div", { style: styles.reviewButtonsRow, children: qualityFailed ? /* @__PURE__ */ jsxs6(Fragment, { children: [
2283
+ /* @__PURE__ */ jsx7("button", { style: { ...styles.darkOutlineButton, flex: 1 }, onClick: handleRetake, children: "Retake" }),
2284
+ canContinueAnyway && /* @__PURE__ */ jsx7("button", { style: { ...styles.primaryButton, flex: 1 }, onClick: handleContinueAnyway, children: "Continue anyway" })
2285
+ ] }) : /* @__PURE__ */ jsxs6(Fragment, { children: [
2286
+ /* @__PURE__ */ jsx7("button", { style: { ...styles.darkOutlineButton, flex: 1 }, onClick: handleRetake, children: "Retake" }),
2287
+ /* @__PURE__ */ jsx7(
1975
2288
  "button",
1976
2289
  {
1977
2290
  style: { ...styles.primaryButton, flex: 1, opacity: isCheckingQuality ? 0.5 : 1 },
@@ -1983,29 +2296,54 @@ function DocumentCaptureScreen({
1983
2296
  ] }) })
1984
2297
  ] });
1985
2298
  }
1986
- return /* @__PURE__ */ jsxs5("div", { style: styles.captureContainer, children: [
1987
- /* @__PURE__ */ jsx6(StepProgressBar, { total: 5, current: 3, isDark: true }),
1988
- /* @__PURE__ */ jsxs5("div", { style: styles.darkScreenHeader, children: [
1989
- /* @__PURE__ */ jsx6("div", { style: { width: 40 } }),
1990
- /* @__PURE__ */ jsxs5("div", { style: { flex: 1, textAlign: "center" }, children: [
1991
- /* @__PURE__ */ jsx6("h1", { style: { ...styles.darkScreenTitle, margin: 0 }, children: side === "front" ? "Front of ID" : "Back of ID" }),
1992
- documentType && /* @__PURE__ */ jsx6("p", { style: styles.darkScreenSubtitle, children: documentType })
2299
+ return /* @__PURE__ */ jsxs6("div", { style: styles.captureContainer, children: [
2300
+ /* @__PURE__ */ jsx7(StepProgressBar, { total: 5, current: 3, isDark: true }),
2301
+ /* @__PURE__ */ jsxs6("div", { style: styles.darkScreenHeader, children: [
2302
+ /* @__PURE__ */ jsx7("div", { style: { width: 40 } }),
2303
+ /* @__PURE__ */ jsxs6("div", { style: { flex: 1, textAlign: "center" }, children: [
2304
+ /* @__PURE__ */ jsx7("h1", { style: { ...styles.darkScreenTitle, margin: 0 }, children: side === "front" ? "Front of ID" : "Back of ID" }),
2305
+ documentType && /* @__PURE__ */ jsx7("p", { style: styles.darkScreenSubtitle, children: documentType })
1993
2306
  ] }),
1994
- /* @__PURE__ */ jsx6("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2307
+ /* @__PURE__ */ jsx7("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
1995
2308
  ] }),
1996
- /* @__PURE__ */ jsxs5("div", { style: styles.cameraContainer, children: [
1997
- /* @__PURE__ */ jsx6("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, style: styles.cameraVideo }),
1998
- /* @__PURE__ */ jsx6("div", { style: styles.documentOverlay, children: /* @__PURE__ */ jsxs5("div", { ref: guideRef, style: styles.documentFrame, children: [
1999
- /* @__PURE__ */ jsx6("div", { style: { ...styles.corner, top: 0, left: 0 } }),
2000
- /* @__PURE__ */ jsx6("div", { style: { ...styles.corner, top: 0, right: 0, transform: "rotate(90deg)" } }),
2001
- /* @__PURE__ */ jsx6("div", { style: { ...styles.corner, bottom: 0, right: 0, transform: "rotate(180deg)" } }),
2002
- /* @__PURE__ */ jsx6("div", { style: { ...styles.corner, bottom: 0, left: 0, transform: "rotate(270deg)" } }),
2003
- /* @__PURE__ */ jsx6("div", { style: styles.scanLine })
2004
- ] }) }),
2005
- /* @__PURE__ */ jsx6("canvas", { ref: canvasRef, style: { display: "none" } })
2309
+ showVisualGuides && /* @__PURE__ */ jsx7("div", { style: { display: "flex", justifyContent: "center", padding: "6px 0 0" }, children: /* @__PURE__ */ jsx7(VisualGuide, { kind: side === "front" ? "docFront" : "docBack", size: 56 }) }),
2310
+ /* @__PURE__ */ jsxs6("div", { style: styles.cameraContainer, children: [
2311
+ /* @__PURE__ */ jsx7("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, style: styles.cameraVideo }),
2312
+ /* @__PURE__ */ jsxs6("div", { style: styles.documentOverlay, children: [
2313
+ /* @__PURE__ */ jsxs6("div", { ref: guideRef, style: styles.documentFrame, children: [
2314
+ /* @__PURE__ */ jsx7("div", { style: { ...styles.corner, top: 0, left: 0 } }),
2315
+ /* @__PURE__ */ jsx7("div", { style: { ...styles.corner, top: 0, right: 0, transform: "rotate(90deg)" } }),
2316
+ /* @__PURE__ */ jsx7("div", { style: { ...styles.corner, bottom: 0, right: 0, transform: "rotate(180deg)" } }),
2317
+ /* @__PURE__ */ jsx7("div", { style: { ...styles.corner, bottom: 0, left: 0, transform: "rotate(270deg)" } }),
2318
+ /* @__PURE__ */ jsx7("div", { style: styles.scanLine })
2319
+ ] }),
2320
+ documentSignals.detectorActive && /* @__PURE__ */ jsx7(
2321
+ "div",
2322
+ {
2323
+ style: {
2324
+ position: "absolute",
2325
+ bottom: "24px",
2326
+ left: "50%",
2327
+ transform: "translateX(-50%)",
2328
+ padding: "6px 14px",
2329
+ borderRadius: "999px",
2330
+ fontSize: "12px",
2331
+ fontWeight: 600,
2332
+ color: documentSignals.documentDetected ? colors.success : "rgba(255,255,255,0.55)",
2333
+ backgroundColor: documentSignals.documentDetected ? "rgba(16,185,129,0.15)" : "rgba(0,0,0,0.35)",
2334
+ border: documentSignals.documentDetected ? "1px solid rgba(16,185,129,0.4)" : "1px solid rgba(255,255,255,0.12)",
2335
+ transition: "all 200ms",
2336
+ pointerEvents: "none",
2337
+ whiteSpace: "nowrap"
2338
+ },
2339
+ children: documentSignals.documentDetected ? "\u2713 Document detected \u2014 fill the frame" : "Position your ID inside the guide"
2340
+ }
2341
+ )
2342
+ ] }),
2343
+ /* @__PURE__ */ jsx7("canvas", { ref: canvasRef, style: { display: "none" } })
2006
2344
  ] }),
2007
- /* @__PURE__ */ jsxs5("div", { style: styles.stepPillsRow, children: [
2008
- /* @__PURE__ */ jsx6(
2345
+ /* @__PURE__ */ jsxs6("div", { style: styles.stepPillsRow, children: [
2346
+ /* @__PURE__ */ jsx7(
2009
2347
  "span",
2010
2348
  {
2011
2349
  style: {
@@ -2016,7 +2354,7 @@ function DocumentCaptureScreen({
2016
2354
  children: "Front"
2017
2355
  }
2018
2356
  ),
2019
- requiresBack && /* @__PURE__ */ jsx6(
2357
+ requiresBack && /* @__PURE__ */ jsx7(
2020
2358
  "span",
2021
2359
  {
2022
2360
  style: {
@@ -2028,7 +2366,7 @@ function DocumentCaptureScreen({
2028
2366
  }
2029
2367
  )
2030
2368
  ] }),
2031
- /* @__PURE__ */ jsx6("div", { style: { textAlign: "center", padding: "8px 0" }, children: /* @__PURE__ */ jsxs5(
2369
+ /* @__PURE__ */ jsx7("div", { style: { textAlign: "center", padding: "8px 0" }, children: /* @__PURE__ */ jsxs6(
2032
2370
  "span",
2033
2371
  {
2034
2372
  style: {
@@ -2037,44 +2375,44 @@ function DocumentCaptureScreen({
2037
2375
  color: colors.teal
2038
2376
  },
2039
2377
  children: [
2040
- /* @__PURE__ */ jsx6("span", { style: { ...styles.pulsingDot, backgroundColor: colors.teal } }),
2378
+ /* @__PURE__ */ jsx7("span", { style: { ...styles.pulsingDot, backgroundColor: colors.teal } }),
2041
2379
  "Scanning document..."
2042
2380
  ]
2043
2381
  }
2044
2382
  ) }),
2045
- /* @__PURE__ */ jsx6("div", { style: styles.captureFooter, children: /* @__PURE__ */ jsx6(
2383
+ /* @__PURE__ */ jsx7("div", { style: styles.captureFooter, children: /* @__PURE__ */ jsx7(
2046
2384
  "button",
2047
2385
  {
2048
2386
  style: { ...styles.captureButton, opacity: isCapturing ? 0.5 : 1 },
2049
2387
  onClick: handleCapture,
2050
2388
  disabled: isCapturing,
2051
- children: /* @__PURE__ */ jsx6("div", { style: styles.captureButtonInner })
2389
+ children: /* @__PURE__ */ jsx7("div", { style: styles.captureButtonInner })
2052
2390
  }
2053
2391
  ) })
2054
2392
  ] });
2055
2393
  }
2056
2394
  function QualityCheck({ label }) {
2057
- return /* @__PURE__ */ jsxs5("div", { style: styles.qualityCheck, children: [
2058
- /* @__PURE__ */ jsx6("div", { style: styles.qualityCheckIcon, children: "\u2713" }),
2059
- /* @__PURE__ */ jsx6("span", { style: styles.qualityCheckLabel, children: label })
2395
+ return /* @__PURE__ */ jsxs6("div", { style: styles.qualityCheck, children: [
2396
+ /* @__PURE__ */ jsx7("div", { style: styles.qualityCheckIcon, children: "\u2713" }),
2397
+ /* @__PURE__ */ jsx7("span", { style: styles.qualityCheckLabel, children: label })
2060
2398
  ] });
2061
2399
  }
2062
2400
 
2063
2401
  // src/components/FlipDocumentScreen.tsx
2064
- import { useEffect as useEffect4 } from "react";
2065
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2402
+ import { useEffect as useEffect6 } from "react";
2403
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2066
2404
  function FlipDocumentScreen({ onContinue, onCancel }) {
2067
- useEffect4(() => {
2405
+ useEffect6(() => {
2068
2406
  injectKeyframes();
2069
2407
  }, []);
2070
- return /* @__PURE__ */ jsxs6("div", { style: styles.darkContainer, children: [
2071
- /* @__PURE__ */ jsx7(StepProgressBar, { total: 5, current: 3, isDark: true }),
2072
- /* @__PURE__ */ jsxs6("div", { style: styles.darkScreenHeader, children: [
2073
- /* @__PURE__ */ jsx7("div", { style: { width: 40 } }),
2074
- /* @__PURE__ */ jsx7("h1", { style: styles.darkScreenTitle, children: "Flip your document" }),
2075
- /* @__PURE__ */ jsx7("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2408
+ return /* @__PURE__ */ jsxs7("div", { style: styles.darkContainer, children: [
2409
+ /* @__PURE__ */ jsx8(StepProgressBar, { total: 5, current: 3, isDark: true }),
2410
+ /* @__PURE__ */ jsxs7("div", { style: styles.darkScreenHeader, children: [
2411
+ /* @__PURE__ */ jsx8("div", { style: { width: 40 } }),
2412
+ /* @__PURE__ */ jsx8("h1", { style: styles.darkScreenTitle, children: "Flip your document" }),
2413
+ /* @__PURE__ */ jsx8("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2076
2414
  ] }),
2077
- /* @__PURE__ */ jsxs6("div", { style: {
2415
+ /* @__PURE__ */ jsxs7("div", { style: {
2078
2416
  flex: 1,
2079
2417
  display: "flex",
2080
2418
  flexDirection: "column",
@@ -2083,7 +2421,7 @@ function FlipDocumentScreen({ onContinue, onCancel }) {
2083
2421
  padding: "24px",
2084
2422
  gap: "32px"
2085
2423
  }, children: [
2086
- /* @__PURE__ */ jsx7("div", { style: {
2424
+ /* @__PURE__ */ jsx8("div", { style: {
2087
2425
  width: "120px",
2088
2426
  height: "120px",
2089
2427
  borderRadius: "50%",
@@ -2091,18 +2429,18 @@ function FlipDocumentScreen({ onContinue, onCancel }) {
2091
2429
  display: "flex",
2092
2430
  alignItems: "center",
2093
2431
  justifyContent: "center"
2094
- }, children: /* @__PURE__ */ jsxs6("svg", { width: "56", height: "56", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
2095
- /* @__PURE__ */ jsx7("path", { d: "M9 3L5 6.99H8V14H10V6.99H13L9 3Z", fill: colors.teal }),
2096
- /* @__PURE__ */ jsx7("path", { d: "M16 17.01V10H14V17.01H11L15 21L19 17.01H16Z", fill: colors.teal })
2432
+ }, children: /* @__PURE__ */ jsxs7("svg", { width: "56", height: "56", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
2433
+ /* @__PURE__ */ jsx8("path", { d: "M9 3L5 6.99H8V14H10V6.99H13L9 3Z", fill: colors.teal }),
2434
+ /* @__PURE__ */ jsx8("path", { d: "M16 17.01V10H14V17.01H11L15 21L19 17.01H16Z", fill: colors.teal })
2097
2435
  ] }) }),
2098
- /* @__PURE__ */ jsxs6("div", { style: { textAlign: "center" }, children: [
2099
- /* @__PURE__ */ jsx7("h2", { style: {
2436
+ /* @__PURE__ */ jsxs7("div", { style: { textAlign: "center" }, children: [
2437
+ /* @__PURE__ */ jsx8("h2", { style: {
2100
2438
  fontSize: "22px",
2101
2439
  fontWeight: 700,
2102
2440
  color: colors.white,
2103
2441
  margin: "0 0 12px 0"
2104
2442
  }, children: "Now capture the back" }),
2105
- /* @__PURE__ */ jsx7("p", { style: {
2443
+ /* @__PURE__ */ jsx8("p", { style: {
2106
2444
  fontSize: "15px",
2107
2445
  color: "rgba(255,255,255,0.6)",
2108
2446
  margin: 0,
@@ -2110,38 +2448,38 @@ function FlipDocumentScreen({ onContinue, onCancel }) {
2110
2448
  maxWidth: "280px"
2111
2449
  }, children: "Turn your document over to the back side, then tap continue to take a photo." })
2112
2450
  ] }),
2113
- /* @__PURE__ */ jsxs6("div", { style: styles.stepPillsRow, children: [
2114
- /* @__PURE__ */ jsx7("span", { style: {
2451
+ /* @__PURE__ */ jsxs7("div", { style: styles.stepPillsRow, children: [
2452
+ /* @__PURE__ */ jsx8("span", { style: {
2115
2453
  ...styles.stepPill,
2116
2454
  backgroundColor: "rgba(16,185,129,0.15)",
2117
2455
  color: colors.success
2118
2456
  }, children: "\u2713 Front" }),
2119
- /* @__PURE__ */ jsx7("span", { style: {
2457
+ /* @__PURE__ */ jsx8("span", { style: {
2120
2458
  ...styles.stepPill,
2121
2459
  backgroundColor: colors.teal,
2122
2460
  color: colors.white
2123
2461
  }, children: "Back" })
2124
2462
  ] })
2125
2463
  ] }),
2126
- /* @__PURE__ */ jsx7("div", { style: { padding: "24px" }, children: /* @__PURE__ */ jsx7("button", { style: styles.primaryButton, onClick: onContinue, children: "Continue" }) })
2464
+ /* @__PURE__ */ jsx8("div", { style: { padding: "24px" }, children: /* @__PURE__ */ jsx8("button", { style: styles.primaryButton, onClick: onContinue, children: "Continue" }) })
2127
2465
  ] });
2128
2466
  }
2129
2467
 
2130
2468
  // src/components/SelfieCaptureScreen.tsx
2131
- import { useRef as useRef3, useEffect as useEffect5, useState as useState4, useCallback as useCallback3 } from "react";
2132
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2133
- function SelfieCaptureScreen({ onCapture, onCancel }) {
2134
- const videoRef = useRef3(null);
2135
- const canvasRef = useRef3(null);
2136
- const [stream, setStream] = useState4(null);
2137
- const [isCapturing, setIsCapturing] = useState4(false);
2138
- const [error, setError] = useState4(null);
2139
- const [capturedImage, setCapturedImage] = useState4(null);
2140
- const [capturedBlob, setCapturedBlob] = useState4(null);
2141
- useEffect5(() => {
2469
+ import { useRef as useRef4, useEffect as useEffect7, useState as useState6, useCallback as useCallback3 } from "react";
2470
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2471
+ function SelfieCaptureScreen({ onCapture, onCancel, showVisualGuides = true }) {
2472
+ const videoRef = useRef4(null);
2473
+ const canvasRef = useRef4(null);
2474
+ const [stream, setStream] = useState6(null);
2475
+ const [isCapturing, setIsCapturing] = useState6(false);
2476
+ const [error, setError] = useState6(null);
2477
+ const [capturedImage, setCapturedImage] = useState6(null);
2478
+ const [capturedBlob, setCapturedBlob] = useState6(null);
2479
+ useEffect7(() => {
2142
2480
  injectKeyframes();
2143
2481
  }, []);
2144
- useEffect5(() => {
2482
+ useEffect7(() => {
2145
2483
  let mounted = true;
2146
2484
  async function startCamera() {
2147
2485
  try {
@@ -2163,7 +2501,7 @@ function SelfieCaptureScreen({ onCapture, onCancel }) {
2163
2501
  mounted = false;
2164
2502
  };
2165
2503
  }, [capturedImage]);
2166
- useEffect5(() => {
2504
+ useEffect7(() => {
2167
2505
  return () => {
2168
2506
  stream?.getTracks().forEach((t) => t.stop());
2169
2507
  };
@@ -2207,22 +2545,22 @@ function SelfieCaptureScreen({ onCapture, onCancel }) {
2207
2545
  }
2208
2546
  };
2209
2547
  if (error) {
2210
- return /* @__PURE__ */ jsx8("div", { style: styles.container, children: /* @__PURE__ */ jsxs7("div", { style: styles.errorContainer, children: [
2211
- /* @__PURE__ */ jsx8("p", { style: styles.errorText, children: error }),
2212
- /* @__PURE__ */ jsx8("button", { style: styles.primaryButton, onClick: onCancel, children: "Go Back" })
2548
+ return /* @__PURE__ */ jsx9("div", { style: styles.container, children: /* @__PURE__ */ jsxs8("div", { style: styles.errorContainer, children: [
2549
+ /* @__PURE__ */ jsx9("p", { style: styles.errorText, children: error }),
2550
+ /* @__PURE__ */ jsx9("button", { style: styles.primaryButton, onClick: onCancel, children: "Go Back" })
2213
2551
  ] }) });
2214
2552
  }
2215
2553
  if (capturedImage) {
2216
- return /* @__PURE__ */ jsxs7("div", { style: styles.darkContainer, children: [
2217
- /* @__PURE__ */ jsx8(StepProgressBar, { total: 5, current: 4, isDark: true }),
2218
- /* @__PURE__ */ jsxs7("div", { style: styles.darkScreenHeader, children: [
2219
- /* @__PURE__ */ jsx8("div", { style: { width: 40 } }),
2220
- /* @__PURE__ */ jsx8("h1", { style: styles.darkScreenTitle, children: "Does this look like you?" }),
2221
- /* @__PURE__ */ jsx8("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2554
+ return /* @__PURE__ */ jsxs8("div", { style: styles.darkContainer, children: [
2555
+ /* @__PURE__ */ jsx9(StepProgressBar, { total: 5, current: 4, isDark: true }),
2556
+ /* @__PURE__ */ jsxs8("div", { style: styles.darkScreenHeader, children: [
2557
+ /* @__PURE__ */ jsx9("div", { style: { width: 40 } }),
2558
+ /* @__PURE__ */ jsx9("h1", { style: styles.darkScreenTitle, children: "Does this look like you?" }),
2559
+ /* @__PURE__ */ jsx9("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2222
2560
  ] }),
2223
- /* @__PURE__ */ jsx8("p", { style: styles.darkScreenSubtitle, children: "Check clarity and lighting" }),
2224
- /* @__PURE__ */ jsx8("div", { style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", padding: "24px" }, children: /* @__PURE__ */ jsxs7("div", { style: { position: "relative" }, children: [
2225
- /* @__PURE__ */ jsx8("div", { style: { width: "240px", height: "300px", borderRadius: "50%", overflow: "hidden", border: `3px solid ${colors.teal}` }, children: /* @__PURE__ */ jsx8(
2561
+ /* @__PURE__ */ jsx9("p", { style: styles.darkScreenSubtitle, children: "Check clarity and lighting" }),
2562
+ /* @__PURE__ */ jsx9("div", { style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", padding: "24px" }, children: /* @__PURE__ */ jsxs8("div", { style: { position: "relative" }, children: [
2563
+ /* @__PURE__ */ jsx9("div", { style: { width: "240px", height: "300px", borderRadius: "50%", overflow: "hidden", border: `3px solid ${colors.teal}` }, children: /* @__PURE__ */ jsx9(
2226
2564
  "img",
2227
2565
  {
2228
2566
  src: capturedImage,
@@ -2230,31 +2568,32 @@ function SelfieCaptureScreen({ onCapture, onCancel }) {
2230
2568
  style: { width: "100%", height: "100%", objectFit: "cover" }
2231
2569
  }
2232
2570
  ) }),
2233
- /* @__PURE__ */ jsx8("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsx8("span", { style: styles.reviewBadge, children: "\u2713 Face detected" }) }),
2234
- /* @__PURE__ */ jsxs7("div", { style: styles.qualityChecks, children: [
2235
- /* @__PURE__ */ jsx8(QualityCheck2, { label: "Clear" }),
2236
- /* @__PURE__ */ jsx8(QualityCheck2, { label: "Centered" }),
2237
- /* @__PURE__ */ jsx8(QualityCheck2, { label: "Well-lit" })
2571
+ /* @__PURE__ */ jsx9("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsx9("span", { style: styles.reviewBadge, children: "\u2713 Face detected" }) }),
2572
+ /* @__PURE__ */ jsxs8("div", { style: styles.qualityChecks, children: [
2573
+ /* @__PURE__ */ jsx9(QualityCheck2, { label: "Clear" }),
2574
+ /* @__PURE__ */ jsx9(QualityCheck2, { label: "Centered" }),
2575
+ /* @__PURE__ */ jsx9(QualityCheck2, { label: "Well-lit" })
2238
2576
  ] })
2239
2577
  ] }) }),
2240
- /* @__PURE__ */ jsxs7("div", { style: styles.reviewButtonsRow, children: [
2241
- /* @__PURE__ */ jsx8("button", { style: { ...styles.darkOutlineButton, flex: 1 }, onClick: handleRetake, children: "Retake" }),
2242
- /* @__PURE__ */ jsx8("button", { style: { ...styles.primaryButton, flex: 1 }, onClick: handleAccept, children: "Use this" })
2578
+ /* @__PURE__ */ jsxs8("div", { style: styles.reviewButtonsRow, children: [
2579
+ /* @__PURE__ */ jsx9("button", { style: { ...styles.darkOutlineButton, flex: 1 }, onClick: handleRetake, children: "Retake" }),
2580
+ /* @__PURE__ */ jsx9("button", { style: { ...styles.primaryButton, flex: 1 }, onClick: handleAccept, children: "Use this" })
2243
2581
  ] })
2244
2582
  ] });
2245
2583
  }
2246
- return /* @__PURE__ */ jsxs7("div", { style: styles.captureContainer, children: [
2247
- /* @__PURE__ */ jsx8(StepProgressBar, { total: 5, current: 4, isDark: true }),
2248
- /* @__PURE__ */ jsxs7("div", { style: styles.darkScreenHeader, children: [
2249
- /* @__PURE__ */ jsx8("div", { style: { width: 40 } }),
2250
- /* @__PURE__ */ jsxs7("div", { style: { flex: 1, textAlign: "center" }, children: [
2251
- /* @__PURE__ */ jsx8("h1", { style: { ...styles.darkScreenTitle, margin: 0, fontSize: "24px", fontWeight: 700 }, children: "Face the camera" }),
2252
- /* @__PURE__ */ jsx8("p", { style: styles.darkScreenSubtitle, children: "Keep a neutral expression" })
2584
+ return /* @__PURE__ */ jsxs8("div", { style: styles.captureContainer, children: [
2585
+ /* @__PURE__ */ jsx9(StepProgressBar, { total: 5, current: 4, isDark: true }),
2586
+ /* @__PURE__ */ jsxs8("div", { style: styles.darkScreenHeader, children: [
2587
+ /* @__PURE__ */ jsx9("div", { style: { width: 40 } }),
2588
+ /* @__PURE__ */ jsxs8("div", { style: { flex: 1, textAlign: "center" }, children: [
2589
+ /* @__PURE__ */ jsx9("h1", { style: { ...styles.darkScreenTitle, margin: 0, fontSize: "24px", fontWeight: 700 }, children: "Face the camera" }),
2590
+ /* @__PURE__ */ jsx9("p", { style: styles.darkScreenSubtitle, children: "Keep a neutral expression" })
2253
2591
  ] }),
2254
- /* @__PURE__ */ jsx8("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2592
+ /* @__PURE__ */ jsx9("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2255
2593
  ] }),
2256
- /* @__PURE__ */ jsxs7("div", { style: styles.cameraContainer, children: [
2257
- /* @__PURE__ */ jsx8(
2594
+ showVisualGuides && /* @__PURE__ */ jsx9("div", { style: { display: "flex", justifyContent: "center", padding: "6px 0 0" }, children: /* @__PURE__ */ jsx9(VisualGuide, { kind: "selfie", size: 56 }) }),
2595
+ /* @__PURE__ */ jsxs8("div", { style: styles.cameraContainer, children: [
2596
+ /* @__PURE__ */ jsx9(
2258
2597
  "video",
2259
2598
  {
2260
2599
  ref: videoRef,
@@ -2264,10 +2603,10 @@ function SelfieCaptureScreen({ onCapture, onCancel }) {
2264
2603
  style: { ...styles.cameraVideo, transform: "scaleX(-1)" }
2265
2604
  }
2266
2605
  ),
2267
- /* @__PURE__ */ jsx8("div", { style: styles.selfieOverlay, children: /* @__PURE__ */ jsx8("div", { style: styles.faceGuide, children: /* @__PURE__ */ jsx8("div", { style: styles.rotatingRing }) }) }),
2268
- /* @__PURE__ */ jsx8("canvas", { ref: canvasRef, style: { display: "none" } })
2606
+ /* @__PURE__ */ jsx9("div", { style: styles.selfieOverlay, children: /* @__PURE__ */ jsx9("div", { style: styles.faceGuide, children: /* @__PURE__ */ jsx9("div", { style: styles.rotatingRing }) }) }),
2607
+ /* @__PURE__ */ jsx9("canvas", { ref: canvasRef, style: { display: "none" } })
2269
2608
  ] }),
2270
- /* @__PURE__ */ jsx8("div", { style: { textAlign: "center", padding: "8px 0" }, children: /* @__PURE__ */ jsxs7(
2609
+ /* @__PURE__ */ jsx9("div", { style: { textAlign: "center", padding: "8px 0" }, children: /* @__PURE__ */ jsxs8(
2271
2610
  "span",
2272
2611
  {
2273
2612
  style: {
@@ -2276,32 +2615,88 @@ function SelfieCaptureScreen({ onCapture, onCancel }) {
2276
2615
  color: colors.teal
2277
2616
  },
2278
2617
  children: [
2279
- /* @__PURE__ */ jsx8("span", { style: { ...styles.pulsingDot, backgroundColor: colors.teal } }),
2618
+ /* @__PURE__ */ jsx9("span", { style: { ...styles.pulsingDot, backgroundColor: colors.teal } }),
2280
2619
  "Position your face in the oval"
2281
2620
  ]
2282
2621
  }
2283
2622
  ) }),
2284
- /* @__PURE__ */ jsx8("div", { style: styles.captureFooter, children: /* @__PURE__ */ jsx8(
2623
+ /* @__PURE__ */ jsx9("div", { style: styles.captureFooter, children: /* @__PURE__ */ jsx9(
2285
2624
  "button",
2286
2625
  {
2287
2626
  style: { ...styles.captureButton, opacity: isCapturing ? 0.5 : 1 },
2288
2627
  onClick: handleCapture,
2289
2628
  disabled: isCapturing,
2290
- children: /* @__PURE__ */ jsx8("div", { style: styles.captureButtonInner })
2629
+ children: /* @__PURE__ */ jsx9("div", { style: styles.captureButtonInner })
2291
2630
  }
2292
2631
  ) })
2293
2632
  ] });
2294
2633
  }
2295
2634
  function QualityCheck2({ label }) {
2296
- return /* @__PURE__ */ jsxs7("div", { style: styles.qualityCheck, children: [
2297
- /* @__PURE__ */ jsx8("div", { style: styles.qualityCheckIcon, children: "\u2713" }),
2298
- /* @__PURE__ */ jsx8("span", { style: styles.qualityCheckLabel, children: label })
2635
+ return /* @__PURE__ */ jsxs8("div", { style: styles.qualityCheck, children: [
2636
+ /* @__PURE__ */ jsx9("div", { style: styles.qualityCheckIcon, children: "\u2713" }),
2637
+ /* @__PURE__ */ jsx9("span", { style: styles.qualityCheckLabel, children: label })
2299
2638
  ] });
2300
2639
  }
2301
2640
 
2302
2641
  // src/components/LivenessScreen.tsx
2303
- import { useEffect as useEffect6, useRef as useRef4, useState as useState5, useCallback as useCallback4 } from "react";
2304
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2642
+ import { useEffect as useEffect9, useRef as useRef6, useState as useState8, useCallback as useCallback4 } from "react";
2643
+
2644
+ // src/hooks/useLivenessSignals.ts
2645
+ import { useEffect as useEffect8, useRef as useRef5, useState as useState7 } from "react";
2646
+ var FRAME_INTERVAL_MS2 = 250;
2647
+ function useLivenessSignals(videoRef) {
2648
+ const [signals, setSignals] = useState7({
2649
+ faceDetected: false,
2650
+ detectorActive: false
2651
+ });
2652
+ const detectorRef = useRef5(null);
2653
+ const intervalRef = useRef5(null);
2654
+ useEffect8(() => {
2655
+ let cancelled = false;
2656
+ async function setupDetector() {
2657
+ const NativeFaceDetector = typeof window !== "undefined" && "FaceDetector" in window ? window.FaceDetector : null;
2658
+ if (!NativeFaceDetector) {
2659
+ return;
2660
+ }
2661
+ try {
2662
+ const detector = new NativeFaceDetector({ fastMode: true, maxDetectedFaces: 1 });
2663
+ if (cancelled) return;
2664
+ detectorRef.current = detector;
2665
+ setSignals((prev) => ({ ...prev, detectorActive: true }));
2666
+ } catch {
2667
+ return;
2668
+ }
2669
+ intervalRef.current = setInterval(async () => {
2670
+ const detector = detectorRef.current;
2671
+ const video = videoRef.current;
2672
+ if (!detector || !video || video.readyState < 2) return;
2673
+ try {
2674
+ const faces = await detector.detect(video);
2675
+ if (cancelled) return;
2676
+ setSignals((prev) => {
2677
+ const next = faces.length > 0;
2678
+ if (prev.faceDetected === next) return prev;
2679
+ return { ...prev, faceDetected: next };
2680
+ });
2681
+ } catch {
2682
+ }
2683
+ }, FRAME_INTERVAL_MS2);
2684
+ }
2685
+ setupDetector();
2686
+ return () => {
2687
+ cancelled = true;
2688
+ if (intervalRef.current) {
2689
+ clearInterval(intervalRef.current);
2690
+ intervalRef.current = null;
2691
+ }
2692
+ detectorRef.current = null;
2693
+ };
2694
+ }, [videoRef]);
2695
+ return signals;
2696
+ }
2697
+
2698
+ // src/components/LivenessScreen.tsx
2699
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2305
2700
  function LivenessScreen({
2306
2701
  session,
2307
2702
  currentChallenge,
@@ -2309,22 +2704,25 @@ function LivenessScreen({
2309
2704
  onChallengeComplete,
2310
2705
  onStart,
2311
2706
  onComplete,
2312
- onCancel
2707
+ onCancel,
2708
+ lastChallengeError,
2709
+ showVisualGuides = true
2313
2710
  }) {
2314
- const videoRef = useRef4(null);
2315
- const canvasRef = useRef4(null);
2316
- const [stream, setStream] = useState5(null);
2317
- const [cameraError, setCameraError] = useState5(null);
2318
- const [phase, setPhase] = useState5("preparing");
2319
- const [countdown, setCountdown] = useState5(3);
2320
- const [capturing, setCapturing] = useState5(false);
2321
- useEffect6(() => {
2711
+ const videoRef = useRef6(null);
2712
+ const canvasRef = useRef6(null);
2713
+ const [stream, setStream] = useState8(null);
2714
+ const livenessSignals = useLivenessSignals(videoRef);
2715
+ const [cameraError, setCameraError] = useState8(null);
2716
+ const [phase, setPhase] = useState8("preparing");
2717
+ const [countdown, setCountdown] = useState8(3);
2718
+ const [capturing, setCapturing] = useState8(false);
2719
+ useEffect9(() => {
2322
2720
  injectKeyframes();
2323
2721
  }, []);
2324
- useEffect6(() => {
2722
+ useEffect9(() => {
2325
2723
  if (!session) onStart();
2326
2724
  }, [session, onStart]);
2327
- useEffect6(() => {
2725
+ useEffect9(() => {
2328
2726
  let mounted = true;
2329
2727
  async function startCamera() {
2330
2728
  try {
@@ -2356,12 +2754,12 @@ function LivenessScreen({
2356
2754
  mounted = false;
2357
2755
  };
2358
2756
  }, []);
2359
- useEffect6(() => {
2757
+ useEffect9(() => {
2360
2758
  return () => {
2361
2759
  stream?.getTracks().forEach((t) => t.stop());
2362
2760
  };
2363
2761
  }, [stream]);
2364
- useEffect6(() => {
2762
+ useEffect9(() => {
2365
2763
  if (!currentChallenge) return;
2366
2764
  setPhase("preparing");
2367
2765
  setCountdown(3);
@@ -2389,7 +2787,7 @@ function LivenessScreen({
2389
2787
  0.85
2390
2788
  );
2391
2789
  }, [currentChallenge, capturing, onChallengeComplete]);
2392
- useEffect6(() => {
2790
+ useEffect9(() => {
2393
2791
  if (!currentChallenge || capturing) return;
2394
2792
  if (countdown === 0) {
2395
2793
  if (phase === "preparing") {
@@ -2403,32 +2801,32 @@ function LivenessScreen({
2403
2801
  const t = setTimeout(() => setCountdown((c) => c - 1), 1e3);
2404
2802
  return () => clearTimeout(t);
2405
2803
  }, [countdown, currentChallenge?.id, capturing, captureFrame, phase]);
2406
- useEffect6(() => {
2804
+ useEffect9(() => {
2407
2805
  if (session && !currentChallenge && completedChallenges > 0) {
2408
2806
  onComplete();
2409
2807
  }
2410
2808
  }, [session, currentChallenge, completedChallenges, onComplete]);
2411
2809
  if (cameraError) {
2412
- return /* @__PURE__ */ jsx9("div", { style: styles.darkContainer, children: /* @__PURE__ */ jsxs8("div", { style: styles.errorContainer, children: [
2413
- /* @__PURE__ */ jsx9("p", { style: styles.errorText, children: cameraError }),
2414
- /* @__PURE__ */ jsx9("button", { style: styles.primaryButton, onClick: onCancel, children: "Go back" })
2810
+ return /* @__PURE__ */ jsx10("div", { style: styles.darkContainer, children: /* @__PURE__ */ jsxs9("div", { style: styles.errorContainer, children: [
2811
+ /* @__PURE__ */ jsx10("p", { style: styles.errorText, children: cameraError }),
2812
+ /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onCancel, children: "Go back" })
2415
2813
  ] }) });
2416
2814
  }
2417
2815
  if (!session) {
2418
- return /* @__PURE__ */ jsx9("div", { style: styles.darkContainer, children: /* @__PURE__ */ jsxs8("div", { style: styles.loadingContainer, children: [
2419
- /* @__PURE__ */ jsx9("div", { style: styles.spinner }),
2420
- /* @__PURE__ */ jsx9("p", { style: { ...styles.loadingText, color: "rgba(255,255,255,0.6)" }, children: "Starting liveness check..." })
2816
+ return /* @__PURE__ */ jsx10("div", { style: styles.darkContainer, children: /* @__PURE__ */ jsxs9("div", { style: styles.loadingContainer, children: [
2817
+ /* @__PURE__ */ jsx10("div", { style: styles.spinner }),
2818
+ /* @__PURE__ */ jsx10("p", { style: { ...styles.loadingText, color: "rgba(255,255,255,0.6)" }, children: "Starting liveness check..." })
2421
2819
  ] }) });
2422
2820
  }
2423
2821
  const totalChallenges = session.challenges.length;
2424
- return /* @__PURE__ */ jsxs8("div", { style: styles.captureContainer, children: [
2425
- /* @__PURE__ */ jsx9(StepProgressBar, { total: 5, current: 5, isDark: true }),
2426
- /* @__PURE__ */ jsxs8("div", { style: styles.darkScreenHeader, children: [
2427
- /* @__PURE__ */ jsx9("div", { style: { width: 40 } }),
2428
- /* @__PURE__ */ jsx9("h1", { style: styles.darkScreenTitle, children: "Liveness Check" }),
2429
- /* @__PURE__ */ jsx9("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2822
+ return /* @__PURE__ */ jsxs9("div", { style: styles.captureContainer, children: [
2823
+ /* @__PURE__ */ jsx10(StepProgressBar, { total: 5, current: 5, isDark: true }),
2824
+ /* @__PURE__ */ jsxs9("div", { style: styles.darkScreenHeader, children: [
2825
+ /* @__PURE__ */ jsx10("div", { style: { width: 40 } }),
2826
+ /* @__PURE__ */ jsx10("h1", { style: styles.darkScreenTitle, children: "Liveness Check" }),
2827
+ /* @__PURE__ */ jsx10("button", { style: styles.glassCloseButton, onClick: onCancel, children: "\u2715" })
2430
2828
  ] }),
2431
- /* @__PURE__ */ jsxs8(
2829
+ /* @__PURE__ */ jsxs9(
2432
2830
  "div",
2433
2831
  {
2434
2832
  style: {
@@ -2441,8 +2839,8 @@ function LivenessScreen({
2441
2839
  padding: "16px 0"
2442
2840
  },
2443
2841
  children: [
2444
- /* @__PURE__ */ jsxs8("div", { style: { position: "relative" }, children: [
2445
- /* @__PURE__ */ jsx9(
2842
+ /* @__PURE__ */ jsxs9("div", { style: { position: "relative" }, children: [
2843
+ /* @__PURE__ */ jsx10(
2446
2844
  "div",
2447
2845
  {
2448
2846
  style: {
@@ -2453,7 +2851,7 @@ function LivenessScreen({
2453
2851
  backgroundColor: "#000",
2454
2852
  border: "3px solid rgba(255,255,255,0.2)"
2455
2853
  },
2456
- children: /* @__PURE__ */ jsx9(
2854
+ children: /* @__PURE__ */ jsx10(
2457
2855
  "video",
2458
2856
  {
2459
2857
  ref: videoRef,
@@ -2470,7 +2868,7 @@ function LivenessScreen({
2470
2868
  )
2471
2869
  }
2472
2870
  ),
2473
- /* @__PURE__ */ jsx9(
2871
+ /* @__PURE__ */ jsx10(
2474
2872
  "svg",
2475
2873
  {
2476
2874
  style: {
@@ -2482,7 +2880,7 @@ function LivenessScreen({
2482
2880
  width: "256",
2483
2881
  height: "316",
2484
2882
  viewBox: "0 0 256 316",
2485
- children: /* @__PURE__ */ jsx9(
2883
+ children: /* @__PURE__ */ jsx10(
2486
2884
  "ellipse",
2487
2885
  {
2488
2886
  cx: "128",
@@ -2498,9 +2896,30 @@ function LivenessScreen({
2498
2896
  }
2499
2897
  )
2500
2898
  }
2899
+ ),
2900
+ livenessSignals.detectorActive && /* @__PURE__ */ jsx10(
2901
+ "div",
2902
+ {
2903
+ style: {
2904
+ position: "absolute",
2905
+ bottom: "-32px",
2906
+ left: "50%",
2907
+ transform: "translateX(-50%)",
2908
+ padding: "6px 14px",
2909
+ borderRadius: "999px",
2910
+ fontSize: "12px",
2911
+ fontWeight: 600,
2912
+ color: livenessSignals.faceDetected ? colors.success : "rgba(255,255,255,0.55)",
2913
+ backgroundColor: livenessSignals.faceDetected ? "rgba(16,185,129,0.15)" : "rgba(255,255,255,0.06)",
2914
+ border: livenessSignals.faceDetected ? "1px solid rgba(16,185,129,0.4)" : "1px solid rgba(255,255,255,0.12)",
2915
+ transition: "all 200ms",
2916
+ whiteSpace: "nowrap"
2917
+ },
2918
+ children: livenessSignals.faceDetected ? "\u2713 Face detected" : "Position your face in the oval"
2919
+ }
2501
2920
  )
2502
2921
  ] }),
2503
- currentChallenge && /* @__PURE__ */ jsxs8(
2922
+ currentChallenge && /* @__PURE__ */ jsxs9(
2504
2923
  "div",
2505
2924
  {
2506
2925
  style: {
@@ -2513,7 +2932,14 @@ function LivenessScreen({
2513
2932
  transition: "background-color 200ms, border-color 200ms"
2514
2933
  },
2515
2934
  children: [
2516
- /* @__PURE__ */ jsx9(
2935
+ showVisualGuides && visualGuideForChallenge(currentChallenge.type) && /* @__PURE__ */ jsx10("div", { style: { display: "flex", justifyContent: "center", marginBottom: "12px" }, children: /* @__PURE__ */ jsx10(
2936
+ VisualGuide,
2937
+ {
2938
+ kind: visualGuideForChallenge(currentChallenge.type),
2939
+ size: 64
2940
+ }
2941
+ ) }),
2942
+ /* @__PURE__ */ jsx10(
2517
2943
  "p",
2518
2944
  {
2519
2945
  style: {
@@ -2527,7 +2953,7 @@ function LivenessScreen({
2527
2953
  children: capturing ? "Checking..." : phase === "preparing" ? "Get ready" : "Now \u2014 hold the pose"
2528
2954
  }
2529
2955
  ),
2530
- /* @__PURE__ */ jsx9(
2956
+ /* @__PURE__ */ jsx10(
2531
2957
  "h2",
2532
2958
  {
2533
2959
  style: {
@@ -2538,7 +2964,7 @@ function LivenessScreen({
2538
2964
  children: currentChallenge.instruction
2539
2965
  }
2540
2966
  ),
2541
- !capturing && /* @__PURE__ */ jsx9(
2967
+ !capturing && /* @__PURE__ */ jsx10(
2542
2968
  "p",
2543
2969
  {
2544
2970
  style: {
@@ -2550,16 +2976,33 @@ function LivenessScreen({
2550
2976
  },
2551
2977
  children: countdown
2552
2978
  }
2979
+ ),
2980
+ lastChallengeError && phase === "preparing" && !capturing && /* @__PURE__ */ jsx10(
2981
+ "p",
2982
+ {
2983
+ style: {
2984
+ margin: "14px 0 0",
2985
+ padding: "10px 12px",
2986
+ borderRadius: "10px",
2987
+ fontSize: "13px",
2988
+ fontWeight: 500,
2989
+ color: "#fca5a5",
2990
+ backgroundColor: "rgba(239,68,68,0.10)",
2991
+ border: "1px solid rgba(239,68,68,0.25)",
2992
+ lineHeight: 1.35
2993
+ },
2994
+ children: lastChallengeError
2995
+ }
2553
2996
  )
2554
2997
  ]
2555
2998
  }
2556
2999
  ),
2557
- /* @__PURE__ */ jsx9("canvas", { ref: canvasRef, style: { display: "none" } })
3000
+ /* @__PURE__ */ jsx10("canvas", { ref: canvasRef, style: { display: "none" } })
2558
3001
  ]
2559
3002
  }
2560
3003
  ),
2561
- /* @__PURE__ */ jsxs8("div", { style: { padding: "16px 0" }, children: [
2562
- /* @__PURE__ */ jsx9("div", { style: styles.progressDots, children: session.challenges.map((_, index) => /* @__PURE__ */ jsx9(
3004
+ /* @__PURE__ */ jsxs9("div", { style: { padding: "16px 0" }, children: [
3005
+ /* @__PURE__ */ jsx10("div", { style: styles.progressDots, children: session.challenges.map((_, index) => /* @__PURE__ */ jsx10(
2563
3006
  "div",
2564
3007
  {
2565
3008
  style: {
@@ -2569,7 +3012,7 @@ function LivenessScreen({
2569
3012
  },
2570
3013
  index
2571
3014
  )) }),
2572
- /* @__PURE__ */ jsxs8("p", { style: styles.progressText, children: [
3015
+ /* @__PURE__ */ jsxs9("p", { style: styles.progressText, children: [
2573
3016
  "Challenge ",
2574
3017
  Math.min(completedChallenges + 1, totalChallenges),
2575
3018
  " of",
@@ -2577,7 +3020,7 @@ function LivenessScreen({
2577
3020
  totalChallenges
2578
3021
  ] })
2579
3022
  ] }),
2580
- /* @__PURE__ */ jsx9("div", { style: { padding: "0 24px 24px", textAlign: "center" }, children: /* @__PURE__ */ jsx9(
3023
+ /* @__PURE__ */ jsx10("div", { style: { padding: "0 24px 24px", textAlign: "center" }, children: /* @__PURE__ */ jsx10(
2581
3024
  "p",
2582
3025
  {
2583
3026
  style: {
@@ -2592,35 +3035,35 @@ function LivenessScreen({
2592
3035
  }
2593
3036
 
2594
3037
  // src/components/ResultScreen.tsx
2595
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3038
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2596
3039
  function ResultScreen({ verification, onDone, onRetry, resultPageMode, simplified, customMessages }) {
2597
3040
  const { status } = verification;
2598
3041
  const effectiveMode = resultPageMode ?? (simplified ? "simplified" : "detailed");
2599
3042
  if (effectiveMode === "simplified") {
2600
3043
  switch (status) {
2601
3044
  case "approved":
2602
- return /* @__PURE__ */ jsx10(SimplifiedSuccess, { onDone, customMessages });
3045
+ return /* @__PURE__ */ jsx11(SimplifiedSuccess, { onDone, customMessages });
2603
3046
  case "rejected":
2604
- return /* @__PURE__ */ jsx10(SimplifiedFailed, { onRetry: onRetry || onDone, customMessages });
3047
+ return /* @__PURE__ */ jsx11(SimplifiedFailed, { onRetry: onRetry || onDone, customMessages });
2605
3048
  case "review_required":
2606
- return /* @__PURE__ */ jsx10(SimplifiedReview, { verification, onDone, customMessages });
3049
+ return /* @__PURE__ */ jsx11(SimplifiedReview, { verification, onDone, customMessages });
2607
3050
  case "expired":
2608
- return /* @__PURE__ */ jsx10(SimplifiedFailed, { onRetry: onRetry || onDone, customMessages: { failedTitle: "Document Expired", failedMessage: "The document you submitted has expired. Please use a valid document." } });
3051
+ return /* @__PURE__ */ jsx11(SimplifiedFailed, { onRetry: onRetry || onDone, customMessages: { failedTitle: "Document Expired", failedMessage: "The document you submitted has expired. Please use a valid document." } });
2609
3052
  default:
2610
- return /* @__PURE__ */ jsx10(SimplifiedSuccess, { onDone, customMessages });
3053
+ return /* @__PURE__ */ jsx11(SimplifiedSuccess, { onDone, customMessages });
2611
3054
  }
2612
3055
  }
2613
3056
  switch (status) {
2614
3057
  case "approved":
2615
- return /* @__PURE__ */ jsx10(SuccessResult, { verification, onDone });
3058
+ return /* @__PURE__ */ jsx11(SuccessResult, { verification, onDone });
2616
3059
  case "rejected":
2617
- return /* @__PURE__ */ jsx10(RejectedResult, { verification, onRetry: onRetry || onDone });
3060
+ return /* @__PURE__ */ jsx11(RejectedResult, { verification, onRetry: onRetry || onDone });
2618
3061
  case "expired":
2619
- return /* @__PURE__ */ jsx10(ExpiredResult, { verification, onRetry: onRetry || onDone });
3062
+ return /* @__PURE__ */ jsx11(ExpiredResult, { verification, onRetry: onRetry || onDone });
2620
3063
  case "review_required":
2621
- return /* @__PURE__ */ jsx10(ManualReviewResult, { verification, onDone });
3064
+ return /* @__PURE__ */ jsx11(ManualReviewResult, { verification, onDone });
2622
3065
  default:
2623
- return /* @__PURE__ */ jsx10(SuccessResult, { verification, onDone });
3066
+ return /* @__PURE__ */ jsx11(SuccessResult, { verification, onDone });
2624
3067
  }
2625
3068
  }
2626
3069
  function SuccessResult({ verification, onDone }) {
@@ -2628,16 +3071,16 @@ function SuccessResult({ verification, onDone }) {
2628
3071
  verification.scores?.overall ?? 100 - (verification.riskScore ?? 16)
2629
3072
  );
2630
3073
  const metrics = computeScoreBreakdown(verification);
2631
- return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2632
- /* @__PURE__ */ jsxs9("div", { style: styles.resultContent, children: [
2633
- /* @__PURE__ */ jsx10(
3074
+ return /* @__PURE__ */ jsxs10("div", { style: styles.resultContainer, children: [
3075
+ /* @__PURE__ */ jsxs10("div", { style: styles.resultContent, children: [
3076
+ /* @__PURE__ */ jsx11(
2634
3077
  "div",
2635
3078
  {
2636
3079
  style: {
2637
3080
  ...styles.resultIconOuterRing,
2638
3081
  backgroundColor: `${colors.success}15`
2639
3082
  },
2640
- children: /* @__PURE__ */ jsx10(
3083
+ children: /* @__PURE__ */ jsx11(
2641
3084
  "div",
2642
3085
  {
2643
3086
  style: {
@@ -2651,9 +3094,9 @@ function SuccessResult({ verification, onDone }) {
2651
3094
  )
2652
3095
  }
2653
3096
  ),
2654
- /* @__PURE__ */ jsx10("h1", { style: styles.resultTitle, children: "Verification approved" }),
2655
- /* @__PURE__ */ jsx10("p", { style: styles.resultSubtitle, children: "Your identity has been successfully verified." }),
2656
- /* @__PURE__ */ jsx10(
3097
+ /* @__PURE__ */ jsx11("h1", { style: styles.resultTitle, children: "Verification approved" }),
3098
+ /* @__PURE__ */ jsx11("p", { style: styles.resultSubtitle, children: "Your identity has been successfully verified." }),
3099
+ /* @__PURE__ */ jsx11(
2657
3100
  ScoreCard,
2658
3101
  {
2659
3102
  score,
@@ -2661,9 +3104,9 @@ function SuccessResult({ verification, onDone }) {
2661
3104
  gradient: `linear-gradient(135deg, ${colors.teal}, ${colors.tealDark})`
2662
3105
  }
2663
3106
  ),
2664
- metrics.map((m, i) => /* @__PURE__ */ jsx10(ScoreMetricRow, { ...m }, i))
3107
+ metrics.map((m, i) => /* @__PURE__ */ jsx11(ScoreMetricRow, { ...m }, i))
2665
3108
  ] }),
2666
- /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onDone, children: "Done" }) })
3109
+ /* @__PURE__ */ jsx11("div", { style: styles.footer, children: /* @__PURE__ */ jsx11("button", { style: styles.primaryButton, onClick: onDone, children: "Done" }) })
2667
3110
  ] });
2668
3111
  }
2669
3112
  function RejectedResult({ verification, onRetry }) {
@@ -2671,16 +3114,16 @@ function RejectedResult({ verification, onRetry }) {
2671
3114
  verification.scores?.overall ?? 100 - (verification.riskScore ?? 58)
2672
3115
  );
2673
3116
  const metrics = computeScoreBreakdown(verification);
2674
- return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2675
- /* @__PURE__ */ jsxs9("div", { style: styles.resultContent, children: [
2676
- /* @__PURE__ */ jsx10(
3117
+ return /* @__PURE__ */ jsxs10("div", { style: styles.resultContainer, children: [
3118
+ /* @__PURE__ */ jsxs10("div", { style: styles.resultContent, children: [
3119
+ /* @__PURE__ */ jsx11(
2677
3120
  "div",
2678
3121
  {
2679
3122
  style: {
2680
3123
  ...styles.resultIconOuterRing,
2681
3124
  backgroundColor: `${colors.error}15`
2682
3125
  },
2683
- children: /* @__PURE__ */ jsx10(
3126
+ children: /* @__PURE__ */ jsx11(
2684
3127
  "div",
2685
3128
  {
2686
3129
  style: {
@@ -2694,9 +3137,9 @@ function RejectedResult({ verification, onRetry }) {
2694
3137
  )
2695
3138
  }
2696
3139
  ),
2697
- /* @__PURE__ */ jsx10("h1", { style: styles.resultTitle, children: "Verification rejected" }),
2698
- /* @__PURE__ */ jsx10("p", { style: styles.resultSubtitle, children: "We could not verify your identity. Please try again with a valid document." }),
2699
- /* @__PURE__ */ jsx10(
3140
+ /* @__PURE__ */ jsx11("h1", { style: styles.resultTitle, children: "Verification rejected" }),
3141
+ /* @__PURE__ */ jsx11("p", { style: styles.resultSubtitle, children: "We could not verify your identity. Please try again with a valid document." }),
3142
+ /* @__PURE__ */ jsx11(
2700
3143
  ScoreCard,
2701
3144
  {
2702
3145
  score,
@@ -2704,15 +3147,15 @@ function RejectedResult({ verification, onRetry }) {
2704
3147
  gradient: `linear-gradient(135deg, ${colors.error}, #B91C1C)`
2705
3148
  }
2706
3149
  ),
2707
- metrics.map((m, i) => /* @__PURE__ */ jsx10(ScoreMetricRow, { ...m }, i))
3150
+ metrics.map((m, i) => /* @__PURE__ */ jsx11(ScoreMetricRow, { ...m }, i))
2708
3151
  ] }),
2709
- /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onRetry, children: "Try again" }) })
3152
+ /* @__PURE__ */ jsx11("div", { style: styles.footer, children: /* @__PURE__ */ jsx11("button", { style: styles.primaryButton, onClick: onRetry, children: "Try again" }) })
2710
3153
  ] });
2711
3154
  }
2712
3155
  function ExpiredResult({ verification, onRetry }) {
2713
- return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2714
- /* @__PURE__ */ jsxs9("div", { style: styles.resultContent, children: [
2715
- /* @__PURE__ */ jsx10(
3156
+ return /* @__PURE__ */ jsxs10("div", { style: styles.resultContainer, children: [
3157
+ /* @__PURE__ */ jsxs10("div", { style: styles.resultContent, children: [
3158
+ /* @__PURE__ */ jsx11(
2716
3159
  "div",
2717
3160
  {
2718
3161
  style: {
@@ -2720,7 +3163,7 @@ function ExpiredResult({ verification, onRetry }) {
2720
3163
  backgroundColor: `${colors.warning}15`,
2721
3164
  border: `2px solid ${colors.warning}30`
2722
3165
  },
2723
- children: /* @__PURE__ */ jsx10(
3166
+ children: /* @__PURE__ */ jsx11(
2724
3167
  "div",
2725
3168
  {
2726
3169
  style: {
@@ -2734,29 +3177,29 @@ function ExpiredResult({ verification, onRetry }) {
2734
3177
  )
2735
3178
  }
2736
3179
  ),
2737
- /* @__PURE__ */ jsx10("h1", { style: styles.resultTitle, children: "Document expired" }),
2738
- /* @__PURE__ */ jsx10("p", { style: styles.resultSubtitle, children: "The document you submitted has expired. Please use a valid, non-expired document." }),
2739
- verification.documentVerification && /* @__PURE__ */ jsxs9("div", { style: styles.expiryCard, children: [
2740
- /* @__PURE__ */ jsxs9("div", { style: styles.expiryRow, children: [
2741
- /* @__PURE__ */ jsx10("span", { style: styles.expiryLabel, children: "Document type" }),
2742
- /* @__PURE__ */ jsx10("span", { style: styles.expiryValue, children: verification.documentVerification.documentType || "ID Card" })
3180
+ /* @__PURE__ */ jsx11("h1", { style: styles.resultTitle, children: "Document expired" }),
3181
+ /* @__PURE__ */ jsx11("p", { style: styles.resultSubtitle, children: "The document you submitted has expired. Please use a valid, non-expired document." }),
3182
+ verification.documentVerification && /* @__PURE__ */ jsxs10("div", { style: styles.expiryCard, children: [
3183
+ /* @__PURE__ */ jsxs10("div", { style: styles.expiryRow, children: [
3184
+ /* @__PURE__ */ jsx11("span", { style: styles.expiryLabel, children: "Document type" }),
3185
+ /* @__PURE__ */ jsx11("span", { style: styles.expiryValue, children: verification.documentVerification.documentType || "ID Card" })
2743
3186
  ] }),
2744
- verification.documentVerification.issuingCountry && /* @__PURE__ */ jsxs9("div", { style: styles.expiryRow, children: [
2745
- /* @__PURE__ */ jsx10("span", { style: styles.expiryLabel, children: "Country" }),
2746
- /* @__PURE__ */ jsx10("span", { style: styles.expiryValue, children: verification.documentVerification.issuingCountry })
3187
+ verification.documentVerification.issuingCountry && /* @__PURE__ */ jsxs10("div", { style: styles.expiryRow, children: [
3188
+ /* @__PURE__ */ jsx11("span", { style: styles.expiryLabel, children: "Country" }),
3189
+ /* @__PURE__ */ jsx11("span", { style: styles.expiryValue, children: verification.documentVerification.issuingCountry })
2747
3190
  ] }),
2748
- verification.documentVerification.expirationDate && /* @__PURE__ */ jsxs9("div", { style: styles.expiryRow, children: [
2749
- /* @__PURE__ */ jsx10("span", { style: styles.expiryLabel, children: "Expired on" }),
2750
- /* @__PURE__ */ jsx10("span", { style: styles.expiryBadge, children: verification.documentVerification.expirationDate })
3191
+ verification.documentVerification.expirationDate && /* @__PURE__ */ jsxs10("div", { style: styles.expiryRow, children: [
3192
+ /* @__PURE__ */ jsx11("span", { style: styles.expiryLabel, children: "Expired on" }),
3193
+ /* @__PURE__ */ jsx11("span", { style: styles.expiryBadge, children: verification.documentVerification.expirationDate })
2751
3194
  ] })
2752
3195
  ] }),
2753
- /* @__PURE__ */ jsxs9("div", { style: { textAlign: "left" }, children: [
2754
- /* @__PURE__ */ jsx10(GuidanceTip, { number: 1, text: "Check the expiration date on your document" }),
2755
- /* @__PURE__ */ jsx10(GuidanceTip, { number: 2, text: "Use a different document that is currently valid" }),
2756
- /* @__PURE__ */ jsx10(GuidanceTip, { number: 3, text: "Ensure the document details are clearly visible" })
3196
+ /* @__PURE__ */ jsxs10("div", { style: { textAlign: "left" }, children: [
3197
+ /* @__PURE__ */ jsx11(GuidanceTip, { number: 1, text: "Check the expiration date on your document" }),
3198
+ /* @__PURE__ */ jsx11(GuidanceTip, { number: 2, text: "Use a different document that is currently valid" }),
3199
+ /* @__PURE__ */ jsx11(GuidanceTip, { number: 3, text: "Ensure the document details are clearly visible" })
2757
3200
  ] })
2758
3201
  ] }),
2759
- /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onRetry, children: "Try with a valid document" }) })
3202
+ /* @__PURE__ */ jsx11("div", { style: styles.footer, children: /* @__PURE__ */ jsx11("button", { style: styles.primaryButton, onClick: onRetry, children: "Try with a valid document" }) })
2760
3203
  ] });
2761
3204
  }
2762
3205
  function ManualReviewResult({ verification, onDone }) {
@@ -2764,9 +3207,9 @@ function ManualReviewResult({ verification, onDone }) {
2764
3207
  verification.scores?.overall ?? 100 - (verification.riskScore ?? 32)
2765
3208
  );
2766
3209
  const metrics = computeScoreBreakdown(verification);
2767
- return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2768
- /* @__PURE__ */ jsxs9("div", { style: styles.resultContent, children: [
2769
- /* @__PURE__ */ jsx10(
3210
+ return /* @__PURE__ */ jsxs10("div", { style: styles.resultContainer, children: [
3211
+ /* @__PURE__ */ jsxs10("div", { style: styles.resultContent, children: [
3212
+ /* @__PURE__ */ jsx11(
2770
3213
  "div",
2771
3214
  {
2772
3215
  style: {
@@ -2774,7 +3217,7 @@ function ManualReviewResult({ verification, onDone }) {
2774
3217
  backgroundColor: `${colors.info}15`,
2775
3218
  border: `2px solid ${colors.info}30`
2776
3219
  },
2777
- children: /* @__PURE__ */ jsx10(
3220
+ children: /* @__PURE__ */ jsx11(
2778
3221
  "div",
2779
3222
  {
2780
3223
  style: {
@@ -2788,9 +3231,9 @@ function ManualReviewResult({ verification, onDone }) {
2788
3231
  )
2789
3232
  }
2790
3233
  ),
2791
- /* @__PURE__ */ jsx10("h1", { style: styles.resultTitle, children: "Under review" }),
2792
- /* @__PURE__ */ jsx10("p", { style: styles.resultSubtitle, children: "Your verification requires manual review. We'll notify you of the result." }),
2793
- /* @__PURE__ */ jsx10(
3234
+ /* @__PURE__ */ jsx11("h1", { style: styles.resultTitle, children: "Under review" }),
3235
+ /* @__PURE__ */ jsx11("p", { style: styles.resultSubtitle, children: "Your verification requires manual review. We'll notify you of the result." }),
3236
+ /* @__PURE__ */ jsx11(
2794
3237
  ScoreCard,
2795
3238
  {
2796
3239
  score,
@@ -2798,21 +3241,21 @@ function ManualReviewResult({ verification, onDone }) {
2798
3241
  gradient: `linear-gradient(135deg, ${colors.info}, #0369A1)`
2799
3242
  }
2800
3243
  ),
2801
- metrics.map((m, i) => /* @__PURE__ */ jsx10(ScoreMetricRow, { ...m }, i))
3244
+ metrics.map((m, i) => /* @__PURE__ */ jsx11(ScoreMetricRow, { ...m }, i))
2802
3245
  ] }),
2803
- /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onDone, children: "Got it" }) })
3246
+ /* @__PURE__ */ jsx11("div", { style: styles.footer, children: /* @__PURE__ */ jsx11("button", { style: styles.primaryButton, onClick: onDone, children: "Got it" }) })
2804
3247
  ] });
2805
3248
  }
2806
3249
  function GuidanceTip({ number, text }) {
2807
- return /* @__PURE__ */ jsxs9("div", { style: styles.guidanceTip, children: [
2808
- /* @__PURE__ */ jsx10("div", { style: styles.guidanceTipNumber, children: number }),
2809
- /* @__PURE__ */ jsx10("span", { style: styles.guidanceTipText, children: text })
3250
+ return /* @__PURE__ */ jsxs10("div", { style: styles.guidanceTip, children: [
3251
+ /* @__PURE__ */ jsx11("div", { style: styles.guidanceTipNumber, children: number }),
3252
+ /* @__PURE__ */ jsx11("span", { style: styles.guidanceTipText, children: text })
2810
3253
  ] });
2811
3254
  }
2812
3255
  function SimplifiedSuccess({ onDone, customMessages }) {
2813
- return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2814
- /* @__PURE__ */ jsxs9("div", { style: { ...styles.resultContent, textAlign: "center" }, children: [
2815
- /* @__PURE__ */ jsx10(
3256
+ return /* @__PURE__ */ jsxs10("div", { style: styles.resultContainer, children: [
3257
+ /* @__PURE__ */ jsxs10("div", { style: { ...styles.resultContent, textAlign: "center" }, children: [
3258
+ /* @__PURE__ */ jsx11(
2816
3259
  "div",
2817
3260
  {
2818
3261
  style: {
@@ -2821,7 +3264,7 @@ function SimplifiedSuccess({ onDone, customMessages }) {
2821
3264
  width: 96,
2822
3265
  height: 96
2823
3266
  },
2824
- children: /* @__PURE__ */ jsx10(
3267
+ children: /* @__PURE__ */ jsx11(
2825
3268
  "div",
2826
3269
  {
2827
3270
  style: {
@@ -2838,16 +3281,16 @@ function SimplifiedSuccess({ onDone, customMessages }) {
2838
3281
  )
2839
3282
  }
2840
3283
  ),
2841
- /* @__PURE__ */ jsx10("h1", { style: { ...styles.resultTitle, fontSize: 24, marginTop: 16 }, children: customMessages?.successTitle || "Verification Successful" }),
2842
- /* @__PURE__ */ jsx10("p", { style: { ...styles.resultSubtitle, fontSize: 16, maxWidth: 320, margin: "8px auto 0" }, children: customMessages?.successMessage || "Your identity has been successfully verified. You can now proceed." })
3284
+ /* @__PURE__ */ jsx11("h1", { style: { ...styles.resultTitle, fontSize: 24, marginTop: 16 }, children: customMessages?.successTitle || "Verification Successful" }),
3285
+ /* @__PURE__ */ jsx11("p", { style: { ...styles.resultSubtitle, fontSize: 16, maxWidth: 320, margin: "8px auto 0" }, children: customMessages?.successMessage || "Your identity has been successfully verified. You can now proceed." })
2843
3286
  ] }),
2844
- /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onDone, children: "Continue" }) })
3287
+ /* @__PURE__ */ jsx11("div", { style: styles.footer, children: /* @__PURE__ */ jsx11("button", { style: styles.primaryButton, onClick: onDone, children: "Continue" }) })
2845
3288
  ] });
2846
3289
  }
2847
3290
  function SimplifiedFailed({ onRetry, customMessages }) {
2848
- return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2849
- /* @__PURE__ */ jsxs9("div", { style: { ...styles.resultContent, textAlign: "center" }, children: [
2850
- /* @__PURE__ */ jsx10(
3291
+ return /* @__PURE__ */ jsxs10("div", { style: styles.resultContainer, children: [
3292
+ /* @__PURE__ */ jsxs10("div", { style: { ...styles.resultContent, textAlign: "center" }, children: [
3293
+ /* @__PURE__ */ jsx11(
2851
3294
  "div",
2852
3295
  {
2853
3296
  style: {
@@ -2856,7 +3299,7 @@ function SimplifiedFailed({ onRetry, customMessages }) {
2856
3299
  width: 96,
2857
3300
  height: 96
2858
3301
  },
2859
- children: /* @__PURE__ */ jsx10(
3302
+ children: /* @__PURE__ */ jsx11(
2860
3303
  "div",
2861
3304
  {
2862
3305
  style: {
@@ -2873,16 +3316,16 @@ function SimplifiedFailed({ onRetry, customMessages }) {
2873
3316
  )
2874
3317
  }
2875
3318
  ),
2876
- /* @__PURE__ */ jsx10("h1", { style: { ...styles.resultTitle, fontSize: 24, marginTop: 16 }, children: customMessages?.failedTitle || "Verification Failed" }),
2877
- /* @__PURE__ */ jsx10("p", { style: { ...styles.resultSubtitle, fontSize: 16, maxWidth: 320, margin: "8px auto 0" }, children: customMessages?.failedMessage || "We could not verify your identity. Please try again with a valid document." })
3319
+ /* @__PURE__ */ jsx11("h1", { style: { ...styles.resultTitle, fontSize: 24, marginTop: 16 }, children: customMessages?.failedTitle || "Verification Failed" }),
3320
+ /* @__PURE__ */ jsx11("p", { style: { ...styles.resultSubtitle, fontSize: 16, maxWidth: 320, margin: "8px auto 0" }, children: customMessages?.failedMessage || "We could not verify your identity. Please try again with a valid document." })
2878
3321
  ] }),
2879
- /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onRetry, children: "Try Again" }) })
3322
+ /* @__PURE__ */ jsx11("div", { style: styles.footer, children: /* @__PURE__ */ jsx11("button", { style: styles.primaryButton, onClick: onRetry, children: "Try Again" }) })
2880
3323
  ] });
2881
3324
  }
2882
3325
  function SimplifiedReview({ verification, onDone, customMessages }) {
2883
- return /* @__PURE__ */ jsxs9("div", { style: styles.resultContainer, children: [
2884
- /* @__PURE__ */ jsxs9("div", { style: { ...styles.resultContent, textAlign: "center" }, children: [
2885
- /* @__PURE__ */ jsx10(
3326
+ return /* @__PURE__ */ jsxs10("div", { style: styles.resultContainer, children: [
3327
+ /* @__PURE__ */ jsxs10("div", { style: { ...styles.resultContent, textAlign: "center" }, children: [
3328
+ /* @__PURE__ */ jsx11(
2886
3329
  "div",
2887
3330
  {
2888
3331
  style: {
@@ -2891,7 +3334,7 @@ function SimplifiedReview({ verification, onDone, customMessages }) {
2891
3334
  width: 96,
2892
3335
  height: 96
2893
3336
  },
2894
- children: /* @__PURE__ */ jsx10(
3337
+ children: /* @__PURE__ */ jsx11(
2895
3338
  "div",
2896
3339
  {
2897
3340
  style: {
@@ -2908,9 +3351,9 @@ function SimplifiedReview({ verification, onDone, customMessages }) {
2908
3351
  )
2909
3352
  }
2910
3353
  ),
2911
- /* @__PURE__ */ jsx10("h1", { style: { ...styles.resultTitle, fontSize: 24, marginTop: 16 }, children: customMessages?.reviewTitle || "Verification Under Review" }),
2912
- /* @__PURE__ */ jsx10("p", { style: { ...styles.resultSubtitle, fontSize: 16, maxWidth: 320, margin: "8px auto 0" }, children: customMessages?.reviewMessage || "Your verification requires additional review. We will notify you of the result." }),
2913
- /* @__PURE__ */ jsxs9("div", { style: {
3354
+ /* @__PURE__ */ jsx11("h1", { style: { ...styles.resultTitle, fontSize: 24, marginTop: 16 }, children: customMessages?.reviewTitle || "Verification Under Review" }),
3355
+ /* @__PURE__ */ jsx11("p", { style: { ...styles.resultSubtitle, fontSize: 16, maxWidth: 320, margin: "8px auto 0" }, children: customMessages?.reviewMessage || "Your verification requires additional review. We will notify you of the result." }),
3356
+ /* @__PURE__ */ jsxs10("div", { style: {
2914
3357
  marginTop: 24,
2915
3358
  padding: "12px 24px",
2916
3359
  backgroundColor: `${colors.info}10`,
@@ -2918,27 +3361,27 @@ function SimplifiedReview({ verification, onDone, customMessages }) {
2918
3361
  border: `1px solid ${colors.info}30`,
2919
3362
  display: "inline-block"
2920
3363
  }, children: [
2921
- /* @__PURE__ */ jsx10("span", { style: { fontSize: 12, color: colors.textSecondary }, children: "Reference: " }),
2922
- /* @__PURE__ */ jsx10("span", { style: { fontSize: 14, fontWeight: 600, fontFamily: "monospace" }, children: verification.id.slice(0, 8) })
3364
+ /* @__PURE__ */ jsx11("span", { style: { fontSize: 12, color: colors.textSecondary }, children: "Reference: " }),
3365
+ /* @__PURE__ */ jsx11("span", { style: { fontSize: 14, fontWeight: 600, fontFamily: "monospace" }, children: verification.id.slice(0, 8) })
2923
3366
  ] })
2924
3367
  ] }),
2925
- /* @__PURE__ */ jsx10("div", { style: styles.footer, children: /* @__PURE__ */ jsx10("button", { style: styles.primaryButton, onClick: onDone, children: "Got It" }) })
3368
+ /* @__PURE__ */ jsx11("div", { style: styles.footer, children: /* @__PURE__ */ jsx11("button", { style: styles.primaryButton, onClick: onDone, children: "Got It" }) })
2926
3369
  ] });
2927
3370
  }
2928
3371
 
2929
3372
  // src/components/ErrorScreen.tsx
2930
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3373
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2931
3374
  function ErrorScreen({ error, onRetry, onCancel }) {
2932
- return /* @__PURE__ */ jsxs10("div", { style: styles.resultContainer, children: [
2933
- /* @__PURE__ */ jsxs10("div", { style: styles.resultContent, children: [
2934
- /* @__PURE__ */ jsx11(
3375
+ return /* @__PURE__ */ jsxs11("div", { style: styles.resultContainer, children: [
3376
+ /* @__PURE__ */ jsxs11("div", { style: styles.resultContent, children: [
3377
+ /* @__PURE__ */ jsx12(
2935
3378
  "div",
2936
3379
  {
2937
3380
  style: {
2938
3381
  ...styles.resultIconOuterRing,
2939
3382
  backgroundColor: colors.errorBg
2940
3383
  },
2941
- children: /* @__PURE__ */ jsx11(
3384
+ children: /* @__PURE__ */ jsx12(
2942
3385
  "div",
2943
3386
  {
2944
3387
  style: {
@@ -2952,26 +3395,26 @@ function ErrorScreen({ error, onRetry, onCancel }) {
2952
3395
  )
2953
3396
  }
2954
3397
  ),
2955
- /* @__PURE__ */ jsx11("h1", { style: styles.resultTitle, children: "Something went wrong" }),
2956
- /* @__PURE__ */ jsx11("p", { style: styles.resultSubtitle, children: error.message }),
2957
- error.recoverySuggestion && /* @__PURE__ */ jsx11("p", { style: { ...styles.bodyText, marginTop: "12px" }, children: error.recoverySuggestion })
3398
+ /* @__PURE__ */ jsx12("h1", { style: styles.resultTitle, children: "Something went wrong" }),
3399
+ /* @__PURE__ */ jsx12("p", { style: styles.resultSubtitle, children: error.message }),
3400
+ error.recoverySuggestion && /* @__PURE__ */ jsx12("p", { style: { ...styles.bodyText, marginTop: "12px" }, children: error.recoverySuggestion })
2958
3401
  ] }),
2959
- /* @__PURE__ */ jsxs10("div", { style: styles.footer, children: [
2960
- error.isRetryable && /* @__PURE__ */ jsx11("button", { style: styles.primaryButton, onClick: onRetry, children: "Try Again" }),
2961
- /* @__PURE__ */ jsx11("button", { style: styles.textButton, onClick: onCancel, children: "Cancel" })
3402
+ /* @__PURE__ */ jsxs11("div", { style: styles.footer, children: [
3403
+ error.isRetryable && /* @__PURE__ */ jsx12("button", { style: styles.primaryButton, onClick: onRetry, children: "Try Again" }),
3404
+ /* @__PURE__ */ jsx12("button", { style: styles.textButton, onClick: onCancel, children: "Cancel" })
2962
3405
  ] })
2963
3406
  ] });
2964
3407
  }
2965
3408
 
2966
3409
  // src/components/LoadingScreen.tsx
2967
- import { useEffect as useEffect7 } from "react";
2968
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3410
+ import { useEffect as useEffect10 } from "react";
3411
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2969
3412
  function LoadingScreen({ message = "Loading..." }) {
2970
- useEffect7(() => {
3413
+ useEffect10(() => {
2971
3414
  injectKeyframes();
2972
3415
  }, []);
2973
- return /* @__PURE__ */ jsx12("div", { style: styles.container, children: /* @__PURE__ */ jsxs11("div", { style: styles.loadingContainer, children: [
2974
- /* @__PURE__ */ jsx12(
3416
+ return /* @__PURE__ */ jsx13("div", { style: styles.container, children: /* @__PURE__ */ jsxs12("div", { style: styles.loadingContainer, children: [
3417
+ /* @__PURE__ */ jsx13(
2975
3418
  "div",
2976
3419
  {
2977
3420
  style: {
@@ -2988,18 +3431,19 @@ function LoadingScreen({ message = "Loading..." }) {
2988
3431
  children: "\u{1F6E1}\uFE0F"
2989
3432
  }
2990
3433
  ),
2991
- /* @__PURE__ */ jsx12("p", { style: styles.loadingText, children: message })
3434
+ /* @__PURE__ */ jsx13("p", { style: styles.loadingText, children: message })
2992
3435
  ] }) });
2993
3436
  }
2994
3437
 
2995
3438
  // src/components/VerificationFlow.tsx
2996
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
3439
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2997
3440
  function VerificationFlow({
2998
3441
  externalId,
2999
3442
  tier = "standard",
3000
3443
  documentTypes,
3001
3444
  expectedFirstName,
3002
3445
  expectedLastName,
3446
+ showVisualGuides = true,
3003
3447
  onComplete,
3004
3448
  onError,
3005
3449
  onCancel,
@@ -3021,20 +3465,20 @@ function VerificationFlow({
3021
3465
  retry,
3022
3466
  sdk
3023
3467
  } = useKoraIDV();
3024
- const [selectedCountry, setSelectedCountry] = useState6(null);
3025
- const [flowStep, setFlowStep] = useState6("consent");
3026
- const [showFlipInstruction, setShowFlipInstruction] = useState6(true);
3027
- const [supportedCountries, setSupportedCountries] = useState6([]);
3028
- const [countriesLoading, setCountriesLoading] = useState6(false);
3029
- useEffect8(() => {
3468
+ const [selectedCountry, setSelectedCountry] = useState9(null);
3469
+ const [flowStep, setFlowStep] = useState9("consent");
3470
+ const [showFlipInstruction, setShowFlipInstruction] = useState9(true);
3471
+ const [supportedCountries, setSupportedCountries] = useState9([]);
3472
+ const [countriesLoading, setCountriesLoading] = useState9(false);
3473
+ useEffect11(() => {
3030
3474
  if (state.step === "document_front") {
3031
3475
  setShowFlipInstruction(true);
3032
3476
  }
3033
3477
  }, [state.step]);
3034
- useEffect8(() => {
3478
+ useEffect11(() => {
3035
3479
  startVerification(externalId, tier, expectedFirstName, expectedLastName);
3036
3480
  }, [externalId, tier, expectedFirstName, expectedLastName, startVerification]);
3037
- useEffect8(() => {
3481
+ useEffect11(() => {
3038
3482
  if (state.error && onError) {
3039
3483
  onError(state.error);
3040
3484
  }
@@ -3080,19 +3524,19 @@ function VerificationFlow({
3080
3524
  ...style
3081
3525
  };
3082
3526
  if (state.error) {
3083
- return /* @__PURE__ */ jsx13("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx13(ErrorScreen, { error: state.error, onRetry: retry, onCancel: handleCancel }) });
3527
+ return /* @__PURE__ */ jsx14("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx14(ErrorScreen, { error: state.error, onRetry: retry, onCancel: handleCancel }) });
3084
3528
  }
3085
3529
  if (state.isLoading && state.step !== "processing") {
3086
- return /* @__PURE__ */ jsx13("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx13(LoadingScreen, {}) });
3530
+ return /* @__PURE__ */ jsx14("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx14(LoadingScreen, {}) });
3087
3531
  }
3088
3532
  if (flowStep === "consent" && state.step === "consent") {
3089
- return /* @__PURE__ */ jsx13("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx13(ConsentScreen, { onAccept: handleAcceptConsent, onDecline: handleCancel }) });
3533
+ return /* @__PURE__ */ jsx14("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx14(ConsentScreen, { onAccept: handleAcceptConsent, onDecline: handleCancel }) });
3090
3534
  }
3091
3535
  if (flowStep === "country_selection") {
3092
3536
  if (countriesLoading) {
3093
- return /* @__PURE__ */ jsx13("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx13(LoadingScreen, {}) });
3537
+ return /* @__PURE__ */ jsx14("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx14(LoadingScreen, {}) });
3094
3538
  }
3095
- return /* @__PURE__ */ jsx13("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx13(
3539
+ return /* @__PURE__ */ jsx14("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx14(
3096
3540
  CountrySelectionScreen,
3097
3541
  {
3098
3542
  countries: supportedCountries,
@@ -3101,8 +3545,8 @@ function VerificationFlow({
3101
3545
  }
3102
3546
  ) });
3103
3547
  }
3104
- return /* @__PURE__ */ jsxs12("div", { className, style: containerStyle, children: [
3105
- state.step === "document_selection" && /* @__PURE__ */ jsx13(
3548
+ return /* @__PURE__ */ jsxs13("div", { className, style: containerStyle, children: [
3549
+ state.step === "document_selection" && /* @__PURE__ */ jsx14(
3106
3550
  DocumentSelectionScreen,
3107
3551
  {
3108
3552
  documentTypes,
@@ -3111,32 +3555,41 @@ function VerificationFlow({
3111
3555
  onCancel: handleCancel
3112
3556
  }
3113
3557
  ),
3114
- state.step === "document_front" && /* @__PURE__ */ jsx13(
3558
+ state.step === "document_front" && /* @__PURE__ */ jsx14(
3115
3559
  DocumentCaptureScreen,
3116
3560
  {
3117
3561
  side: "front",
3118
3562
  onQualityCheck: (blob) => checkDocumentQuality(blob),
3119
3563
  onCapture: (imageData) => uploadDocument(imageData, "front", selectedCountry?.id),
3120
- onCancel: handleCancel
3564
+ onCancel: handleCancel,
3565
+ showVisualGuides
3121
3566
  }
3122
3567
  ),
3123
- state.step === "document_back" && showFlipInstruction && /* @__PURE__ */ jsx13(
3568
+ state.step === "document_back" && showFlipInstruction && /* @__PURE__ */ jsx14(
3124
3569
  FlipDocumentScreen,
3125
3570
  {
3126
3571
  onContinue: () => setShowFlipInstruction(false),
3127
3572
  onCancel: handleCancel
3128
3573
  }
3129
3574
  ),
3130
- state.step === "document_back" && !showFlipInstruction && /* @__PURE__ */ jsx13(
3575
+ state.step === "document_back" && !showFlipInstruction && /* @__PURE__ */ jsx14(
3131
3576
  DocumentCaptureScreen,
3132
3577
  {
3133
3578
  side: "back",
3134
3579
  onCapture: (imageData) => uploadDocument(imageData, "back", selectedCountry?.id),
3135
- onCancel: handleCancel
3580
+ onCancel: handleCancel,
3581
+ showVisualGuides
3136
3582
  }
3137
3583
  ),
3138
- state.step === "selfie" && /* @__PURE__ */ jsx13(SelfieCaptureScreen, { onCapture: uploadSelfie, onCancel: handleCancel }),
3139
- state.step === "liveness" && /* @__PURE__ */ jsx13(
3584
+ state.step === "selfie" && /* @__PURE__ */ jsx14(
3585
+ SelfieCaptureScreen,
3586
+ {
3587
+ onCapture: uploadSelfie,
3588
+ onCancel: handleCancel,
3589
+ showVisualGuides
3590
+ }
3591
+ ),
3592
+ state.step === "liveness" && /* @__PURE__ */ jsx14(
3140
3593
  LivenessScreen,
3141
3594
  {
3142
3595
  session: state.livenessSession,
@@ -3145,20 +3598,13 @@ function VerificationFlow({
3145
3598
  onChallengeComplete: submitChallenge,
3146
3599
  onStart: startLiveness,
3147
3600
  onComplete: complete,
3148
- onCancel: handleCancel
3601
+ onCancel: handleCancel,
3602
+ lastChallengeError: state.lastChallengeError,
3603
+ showVisualGuides
3149
3604
  }
3150
3605
  ),
3151
- state.step === "processing" && /* @__PURE__ */ jsx13(
3152
- ProcessingScreen,
3153
- {
3154
- steps: [
3155
- { label: "Document analyzed", status: "done" },
3156
- { label: "Checking face match", status: "active" },
3157
- { label: "Finalizing results", status: "pending" }
3158
- ]
3159
- }
3160
- ),
3161
- state.step === "complete" && state.verification && /* @__PURE__ */ jsx13(
3606
+ state.step === "processing" && /* @__PURE__ */ jsx14(ProcessingScreen, {}),
3607
+ state.step === "complete" && state.verification && /* @__PURE__ */ jsx14(
3162
3608
  ResultScreen,
3163
3609
  {
3164
3610
  verification: state.verification,
@@ -3170,8 +3616,8 @@ function VerificationFlow({
3170
3616
  }
3171
3617
 
3172
3618
  // src/components/QrHandoffScreen.tsx
3173
- import { useEffect as useEffect9, useState as useState7, useRef as useRef5 } from "react";
3174
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
3619
+ import { useEffect as useEffect12, useState as useState10, useRef as useRef7 } from "react";
3620
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
3175
3621
  function QrHandoffScreen({
3176
3622
  session,
3177
3623
  onMobileCaptureComplete,
@@ -3180,11 +3626,11 @@ function QrHandoffScreen({
3180
3626
  onRefresh,
3181
3627
  eventSource
3182
3628
  }) {
3183
- const [timeLeft, setTimeLeft] = useState7(session.expiresIn);
3184
- const [scanned, setScanned] = useState7(false);
3185
- const [expired, setExpired] = useState7(false);
3186
- const timerRef = useRef5();
3187
- useEffect9(() => {
3629
+ const [timeLeft, setTimeLeft] = useState10(session.expiresIn);
3630
+ const [scanned, setScanned] = useState10(false);
3631
+ const [expired, setExpired] = useState10(false);
3632
+ const timerRef = useRef7();
3633
+ useEffect12(() => {
3188
3634
  setTimeLeft(session.expiresIn);
3189
3635
  setExpired(false);
3190
3636
  setScanned(false);
@@ -3201,7 +3647,7 @@ function QrHandoffScreen({
3201
3647
  }, 1e3);
3202
3648
  return () => clearInterval(timerRef.current);
3203
3649
  }, [session.token]);
3204
- useEffect9(() => {
3650
+ useEffect12(() => {
3205
3651
  if (!eventSource) return;
3206
3652
  const handleStatus = (event) => {
3207
3653
  try {
@@ -3227,34 +3673,34 @@ function QrHandoffScreen({
3227
3673
  const seconds = timeLeft % 60;
3228
3674
  const qrSize = 200;
3229
3675
  if (expired) {
3230
- return /* @__PURE__ */ jsx14("div", { style: qrStyles.container, children: /* @__PURE__ */ jsxs13("div", { style: qrStyles.content, children: [
3231
- /* @__PURE__ */ jsx14("div", { style: qrStyles.expiredIcon, children: "\u23F1" }),
3232
- /* @__PURE__ */ jsx14("h2", { style: qrStyles.title, children: "QR Code Expired" }),
3233
- /* @__PURE__ */ jsx14("p", { style: qrStyles.subtitle, children: "The QR code has expired. Generate a new one to continue." }),
3234
- /* @__PURE__ */ jsx14("button", { style: qrStyles.primaryButton, onClick: onRefresh, children: "Generate New QR Code" }),
3235
- /* @__PURE__ */ jsx14("button", { style: qrStyles.secondaryButton, onClick: onContinueOnDevice, children: "Continue on this device instead" })
3676
+ return /* @__PURE__ */ jsx15("div", { style: qrStyles.container, children: /* @__PURE__ */ jsxs14("div", { style: qrStyles.content, children: [
3677
+ /* @__PURE__ */ jsx15("div", { style: qrStyles.expiredIcon, children: "\u23F1" }),
3678
+ /* @__PURE__ */ jsx15("h2", { style: qrStyles.title, children: "QR Code Expired" }),
3679
+ /* @__PURE__ */ jsx15("p", { style: qrStyles.subtitle, children: "The QR code has expired. Generate a new one to continue." }),
3680
+ /* @__PURE__ */ jsx15("button", { style: qrStyles.primaryButton, onClick: onRefresh, children: "Generate New QR Code" }),
3681
+ /* @__PURE__ */ jsx15("button", { style: qrStyles.secondaryButton, onClick: onContinueOnDevice, children: "Continue on this device instead" })
3236
3682
  ] }) });
3237
3683
  }
3238
3684
  if (scanned) {
3239
- return /* @__PURE__ */ jsx14("div", { style: qrStyles.container, children: /* @__PURE__ */ jsxs13("div", { style: qrStyles.content, children: [
3240
- /* @__PURE__ */ jsx14("div", { style: qrStyles.spinnerContainer, children: /* @__PURE__ */ jsx14("div", { style: qrStyles.spinner }) }),
3241
- /* @__PURE__ */ jsx14("h2", { style: qrStyles.title, children: "Capturing on your phone..." }),
3242
- /* @__PURE__ */ jsx14("p", { style: qrStyles.subtitle, children: "Complete the document scan and selfie on your mobile device. This page will update automatically when done." }),
3243
- /* @__PURE__ */ jsxs13("div", { style: qrStyles.statusBadge, children: [
3244
- /* @__PURE__ */ jsx14("span", { style: qrStyles.statusDot }),
3685
+ return /* @__PURE__ */ jsx15("div", { style: qrStyles.container, children: /* @__PURE__ */ jsxs14("div", { style: qrStyles.content, children: [
3686
+ /* @__PURE__ */ jsx15("div", { style: qrStyles.spinnerContainer, children: /* @__PURE__ */ jsx15("div", { style: qrStyles.spinner }) }),
3687
+ /* @__PURE__ */ jsx15("h2", { style: qrStyles.title, children: "Capturing on your phone..." }),
3688
+ /* @__PURE__ */ jsx15("p", { style: qrStyles.subtitle, children: "Complete the document scan and selfie on your mobile device. This page will update automatically when done." }),
3689
+ /* @__PURE__ */ jsxs14("div", { style: qrStyles.statusBadge, children: [
3690
+ /* @__PURE__ */ jsx15("span", { style: qrStyles.statusDot }),
3245
3691
  "Connected \u2014 waiting for capture"
3246
3692
  ] })
3247
3693
  ] }) });
3248
3694
  }
3249
- return /* @__PURE__ */ jsx14("div", { style: qrStyles.container, children: /* @__PURE__ */ jsxs13("div", { style: qrStyles.content, children: [
3250
- /* @__PURE__ */ jsx14("h2", { style: qrStyles.title, children: "Scan with your phone" }),
3251
- /* @__PURE__ */ jsx14("p", { style: qrStyles.subtitle, children: "Use your phone's camera for a better capture experience. Scan the QR code below to continue on your mobile device." }),
3252
- /* @__PURE__ */ jsxs13("div", { style: qrStyles.qrContainer, children: [
3253
- /* @__PURE__ */ jsx14("div", { style: {
3695
+ return /* @__PURE__ */ jsx15("div", { style: qrStyles.container, children: /* @__PURE__ */ jsxs14("div", { style: qrStyles.content, children: [
3696
+ /* @__PURE__ */ jsx15("h2", { style: qrStyles.title, children: "Scan with your phone" }),
3697
+ /* @__PURE__ */ jsx15("p", { style: qrStyles.subtitle, children: "Use your phone's camera for a better capture experience. Scan the QR code below to continue on your mobile device." }),
3698
+ /* @__PURE__ */ jsxs14("div", { style: qrStyles.qrContainer, children: [
3699
+ /* @__PURE__ */ jsx15("div", { style: {
3254
3700
  ...qrStyles.qrBox,
3255
3701
  width: qrSize,
3256
3702
  height: qrSize
3257
- }, children: /* @__PURE__ */ jsx14(
3703
+ }, children: /* @__PURE__ */ jsx15(
3258
3704
  "img",
3259
3705
  {
3260
3706
  src: `https://api.qrserver.com/v1/create-qr-code/?size=${qrSize}x${qrSize}&data=${encodeURIComponent(session.captureUrl)}&margin=8`,
@@ -3264,26 +3710,26 @@ function QrHandoffScreen({
3264
3710
  style: { borderRadius: 12 }
3265
3711
  }
3266
3712
  ) }),
3267
- /* @__PURE__ */ jsxs13("div", { style: qrStyles.timer, children: [
3713
+ /* @__PURE__ */ jsxs14("div", { style: qrStyles.timer, children: [
3268
3714
  minutes,
3269
3715
  ":",
3270
3716
  seconds.toString().padStart(2, "0"),
3271
3717
  " remaining"
3272
3718
  ] })
3273
3719
  ] }),
3274
- /* @__PURE__ */ jsxs13("div", { style: qrStyles.steps, children: [
3275
- /* @__PURE__ */ jsx14(Step, { number: 1, text: "Open your phone's camera" }),
3276
- /* @__PURE__ */ jsx14(Step, { number: 2, text: "Point at the QR code" }),
3277
- /* @__PURE__ */ jsx14(Step, { number: 3, text: "Complete the capture on your phone" })
3720
+ /* @__PURE__ */ jsxs14("div", { style: qrStyles.steps, children: [
3721
+ /* @__PURE__ */ jsx15(Step, { number: 1, text: "Open your phone's camera" }),
3722
+ /* @__PURE__ */ jsx15(Step, { number: 2, text: "Point at the QR code" }),
3723
+ /* @__PURE__ */ jsx15(Step, { number: 3, text: "Complete the capture on your phone" })
3278
3724
  ] }),
3279
- /* @__PURE__ */ jsx14("div", { style: qrStyles.divider, children: /* @__PURE__ */ jsx14("span", { style: qrStyles.dividerText, children: "or" }) }),
3280
- /* @__PURE__ */ jsx14("button", { style: qrStyles.secondaryButton, onClick: onContinueOnDevice, children: "Continue on this device" })
3725
+ /* @__PURE__ */ jsx15("div", { style: qrStyles.divider, children: /* @__PURE__ */ jsx15("span", { style: qrStyles.dividerText, children: "or" }) }),
3726
+ /* @__PURE__ */ jsx15("button", { style: qrStyles.secondaryButton, onClick: onContinueOnDevice, children: "Continue on this device" })
3281
3727
  ] }) });
3282
3728
  }
3283
3729
  function Step({ number, text }) {
3284
- return /* @__PURE__ */ jsxs13("div", { style: qrStyles.step, children: [
3285
- /* @__PURE__ */ jsx14("div", { style: qrStyles.stepNumber, children: number }),
3286
- /* @__PURE__ */ jsx14("span", { style: qrStyles.stepText, children: text })
3730
+ return /* @__PURE__ */ jsxs14("div", { style: qrStyles.step, children: [
3731
+ /* @__PURE__ */ jsx15("div", { style: qrStyles.stepNumber, children: number }),
3732
+ /* @__PURE__ */ jsx15("span", { style: qrStyles.stepText, children: text })
3287
3733
  ] });
3288
3734
  }
3289
3735
  var qrStyles = {