vesant-sdk 1.7.1-dev.2a5fd70 → 1.7.1-dev.2b64301

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/react.js CHANGED
@@ -930,19 +930,19 @@ var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0id
930
930
  var FACE_MESSAGES = {
931
931
  idle: "Initializing camera...",
932
932
  loading: "Loading face detection...",
933
- scanning: "Position your face in the circle",
933
+ scanning: "Position your face in the oval",
934
934
  too_far: "Face too far. Please move closer to the camera",
935
935
  too_close: "Too close. Please move back a little",
936
- off_center: "Center your face in the circle",
936
+ off_center: "Center your face in the oval",
937
937
  ok: "Looking great! Tap the button to capture"
938
938
  };
939
939
  var FACE_COLORS = {
940
940
  idle: "#9ca3af",
941
- loading: "#3b82f6",
942
- scanning: "#3b82f6",
941
+ loading: "#ef4444",
942
+ scanning: "#ef4444",
943
943
  too_far: "#ef4444",
944
- too_close: "#f59e0b",
945
- off_center: "#f59e0b",
944
+ too_close: "#ef4444",
945
+ off_center: "#ef4444",
946
946
  ok: "#10b981"
947
947
  };
948
948
  var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
@@ -1007,11 +1007,32 @@ function loadMediaPipeFaceDetection(assetBasePath) {
1007
1007
  return loader;
1008
1008
  }
1009
1009
 
1010
+ // src/kyc/hostedJourney.ts
1011
+ var REUSE_KYC_SESSION_EXPIRY_MS = 10 * 60 * 1e3;
1012
+ function tryStartOperation(guard) {
1013
+ if (guard.current) return false;
1014
+ guard.current = true;
1015
+ return true;
1016
+ }
1017
+ function releaseOperation(guard) {
1018
+ guard.current = false;
1019
+ }
1020
+ function retryTarget(hasHostedJourney) {
1021
+ return hasHostedJourney ? "journey" : "capture";
1022
+ }
1023
+ function modalMaxWidth(hasHostedJourney) {
1024
+ return hasHostedJourney ? 680 : 448;
1025
+ }
1026
+ function resultForModalClose(stageKind, result) {
1027
+ const terminal = stageKind === "accepted" || stageKind === "declined" || stageKind === "max_attempts";
1028
+ return terminal && result !== void 0 ? result : null;
1029
+ }
1030
+
1010
1031
  // src/kyc/FaceCaptureModal.tsx
1011
1032
  var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
1012
- var SESSION_EXPIRY_MS = 15 * 60 * 1e3;
1033
+ var ACCEPTED_AUTO_CLOSE_MS = 1200;
1013
1034
  var LIVENESS_MESSAGES = [
1014
- "Position your face inside the circle",
1035
+ "Position your face inside the oval",
1015
1036
  "Make sure your face is well lit",
1016
1037
  "Remove glasses or hats if possible",
1017
1038
  "Look straight at the camera, then tap Capture"
@@ -1020,6 +1041,8 @@ function headerSubtitle(stageKind) {
1020
1041
  switch (stageKind) {
1021
1042
  case "qr":
1022
1043
  return "Use your phone to continue";
1044
+ case "journey":
1045
+ return "Follow the steps to verify your identity";
1023
1046
  case "accepted":
1024
1047
  return "All set";
1025
1048
  case "declined":
@@ -1045,7 +1068,9 @@ function FaceCaptureModal({
1045
1068
  }) {
1046
1069
  const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
1047
1070
  const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
1071
+ const journeyMode = Boolean(session.verification_url);
1048
1072
  const initialStage = (() => {
1073
+ if (journeyMode) return { kind: "journey", url: session.verification_url ?? "" };
1049
1074
  if (initialChoice === "this") return { kind: "capture" };
1050
1075
  if (initialChoice === "mobile") return { kind: "qr", mobileConnected: false };
1051
1076
  return { kind: "choose" };
@@ -1063,19 +1088,20 @@ function FaceCaptureModal({
1063
1088
  const qrPayload = session.link || `vesant://reuse-kyc?token=${encodeURIComponent(session.token)}`;
1064
1089
  const cancelledRef = React.useRef(false);
1065
1090
  const submittingRef = React.useRef(false);
1091
+ const journeyRetryingRef = React.useRef(false);
1066
1092
  React.useEffect(() => {
1067
1093
  const timer = setTimeout(() => {
1068
1094
  setStage(
1069
- (prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" ? { kind: "error", message: "Session expired" } : prev
1095
+ (prev) => prev.kind === "choose" || prev.kind === "qr" || prev.kind === "capture" || prev.kind === "journey" ? { kind: "error", message: "Session expired" } : prev
1070
1096
  );
1071
- }, SESSION_EXPIRY_MS);
1097
+ }, REUSE_KYC_SESSION_EXPIRY_MS);
1072
1098
  return () => clearTimeout(timer);
1073
1099
  }, [session.token]);
1074
1100
  React.useEffect(() => {
1075
1101
  if (stage.kind !== "qr") return;
1076
1102
  let stopped = false;
1077
1103
  const intervalMs = 2e3;
1078
- const deadline = Date.now() + SESSION_EXPIRY_MS;
1104
+ const deadline = Date.now() + REUSE_KYC_SESSION_EXPIRY_MS;
1079
1105
  let mobileSeen = stage.mobileConnected;
1080
1106
  const tick = async () => {
1081
1107
  if (stopped || cancelledRef.current) return;
@@ -1116,6 +1142,64 @@ function FaceCaptureModal({
1116
1142
  stopped = true;
1117
1143
  };
1118
1144
  }, [stage.kind, stage.kind === "qr" ? stage.mobileConnected : false]);
1145
+ React.useEffect(() => {
1146
+ if (stage.kind !== "journey" || !stage.url) return;
1147
+ let stopped = false;
1148
+ const checkOnce = async () => {
1149
+ try {
1150
+ const result = await client.getEventBasedFaceVerificationSessionStatus(session.token);
1151
+ if (applyResult(result)) {
1152
+ stopped = true;
1153
+ return true;
1154
+ }
1155
+ } catch {
1156
+ }
1157
+ return false;
1158
+ };
1159
+ const tick = async () => {
1160
+ if (stopped || cancelledRef.current) return;
1161
+ await checkOnce();
1162
+ if (!stopped && !cancelledRef.current) {
1163
+ setTimeout(tick, 2500);
1164
+ }
1165
+ };
1166
+ void tick();
1167
+ let frameOrigin = "";
1168
+ try {
1169
+ frameOrigin = new URL(stage.url).origin;
1170
+ } catch {
1171
+ frameOrigin = "";
1172
+ }
1173
+ const onMessage = (e) => {
1174
+ if (!frameOrigin || e.origin !== frameOrigin) return;
1175
+ const status = e.data?.verification_status;
1176
+ if (status) void checkOnce();
1177
+ };
1178
+ window.addEventListener("message", onMessage);
1179
+ return () => {
1180
+ stopped = true;
1181
+ window.removeEventListener("message", onMessage);
1182
+ };
1183
+ }, [stage.kind, stage.kind === "journey" ? stage.url : ""]);
1184
+ const handleJourneyRetry = async () => {
1185
+ if (!tryStartOperation(journeyRetryingRef)) return;
1186
+ setStage({ kind: "journey", url: "" });
1187
+ try {
1188
+ const refreshed = await client.retryEventBasedFaceVerificationJourney(session.token);
1189
+ if (refreshed.verification_url) {
1190
+ setStage({ kind: "journey", url: refreshed.verification_url });
1191
+ return;
1192
+ }
1193
+ setStage({ kind: "error", message: "Unable to start a new attempt. Please try again." });
1194
+ } catch (err) {
1195
+ setStage({
1196
+ kind: "error",
1197
+ message: err instanceof Error ? err.message : "Unable to start a new attempt. Please try again."
1198
+ });
1199
+ } finally {
1200
+ releaseOperation(journeyRetryingRef);
1201
+ }
1202
+ };
1119
1203
  const applyResult = (result) => {
1120
1204
  if (result.status === "accepted") {
1121
1205
  setStage({ kind: "accepted", result });
@@ -1243,12 +1327,22 @@ function FaceCaptureModal({
1243
1327
  }, 3e3);
1244
1328
  return () => clearInterval(interval);
1245
1329
  }, [captureMode]);
1246
- const close = (result) => {
1330
+ const close = React.useCallback((result) => {
1247
1331
  cancelledRef.current = true;
1248
1332
  if (result === null && onCancel) {
1249
1333
  onCancel();
1250
1334
  }
1251
1335
  onComplete(result);
1336
+ }, [onCancel, onComplete]);
1337
+ React.useEffect(() => {
1338
+ if (stage.kind !== "accepted") return;
1339
+ const result = stage.result;
1340
+ const timer = setTimeout(() => close(result), ACCEPTED_AUTO_CLOSE_MS);
1341
+ return () => clearTimeout(timer);
1342
+ }, [close, stage]);
1343
+ const handleHeaderClose = () => {
1344
+ const result = "result" in stage ? stage.result : void 0;
1345
+ close(resultForModalClose(stage.kind, result));
1252
1346
  };
1253
1347
  const renderChoose = () => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideContainerStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: circleStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 48, height: 48 })))), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "How would you like to verify?"), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement(
1254
1348
  "button",
@@ -1307,12 +1401,28 @@ function FaceCaptureModal({
1307
1401
  };
1308
1402
  const renderSubmitting = () => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: { ...spinnerStyle, margin: "24px auto", width: 32, height: 32 } }), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Verifying your photo\u2026"), /* @__PURE__ */ React__default.default.createElement("p", { style: { ...attemptsTextStyle, marginTop: 8 } }, "This usually takes a few seconds."));
1309
1403
  const renderAccepted = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: successCircleStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Verified"), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Your identity has been confirmed. You can continue."), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Continue")));
1310
- const renderDeclined = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, result.declined_reason ?? "We couldn't match your selfie. Please take a new one.")), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleRetakeAfterDecline }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, "Retake")), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(result) }, "Cancel")), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
1404
+ const renderDeclined = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, result.declined_reason ?? "We couldn't match your selfie. Please take a new one.")), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, journeyMode ? /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => void handleJourneyRetry() }, /* @__PURE__ */ React__default.default.createElement("span", null, "Try Again")) : /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleRetakeAfterDecline }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, "Retake")), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(result) }, "Cancel")), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
1311
1405
  const renderMaxAttempts = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Maximum Attempts Reached"), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, result.declined_reason ?? "We couldn't verify your identity after several attempts."), result.data?.freeze_account && /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, "For your security, your account has been temporarily restricted", result.data.freeze_duration_minutes ? ` for ${result.data.freeze_duration_minutes} minutes` : "", ". Please contact support if you need immediate help."), result.data?.enforce_logout && /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, "You will be signed out of your session."), result.data?.block && /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, "This action has been blocked for security reasons."), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Close")));
1312
1406
  const renderError = (message) => {
1313
1407
  const sessionExpired = /session\s+expired/i.test(message);
1314
- return /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, sessionExpired ? "Session expired" : "Something Went Wrong"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, sessionExpired ? "Your verification session is no longer valid. Please start a new verification from the beginning." : message), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, !sessionExpired && /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => setStage({ kind: "capture" }) }, "Try Again"), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
1408
+ return /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, sessionExpired ? "Session expired" : "Something Went Wrong"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, sessionExpired ? "Your verification session is no longer valid. Please start a new verification from the beginning." : message), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, !sessionExpired && /* @__PURE__ */ React__default.default.createElement(
1409
+ "button",
1410
+ {
1411
+ style: primaryButtonStyle,
1412
+ onClick: () => retryTarget(journeyMode) === "journey" ? void handleJourneyRetry() : setStage({ kind: "capture" })
1413
+ },
1414
+ "Try Again"
1415
+ ), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
1315
1416
  };
1417
+ const renderJourney = (url) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, url ? /* @__PURE__ */ React__default.default.createElement(
1418
+ "iframe",
1419
+ {
1420
+ src: url,
1421
+ title: "Identity verification",
1422
+ allow: "camera; microphone",
1423
+ style: journeyFrameStyle
1424
+ }
1425
+ ) : /* @__PURE__ */ React__default.default.createElement("div", { style: { padding: 24, textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: { ...spinnerStyle, margin: "24px auto", width: 32, height: 32 } }), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Preparing your next attempt\u2026")), /* @__PURE__ */ React__default.default.createElement("p", { style: { ...attemptsTextStyle, textAlign: "center" } }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
1316
1426
  const body = (() => {
1317
1427
  switch (stage.kind) {
1318
1428
  case "choose":
@@ -1321,6 +1431,8 @@ function FaceCaptureModal({
1321
1431
  return renderQRStage(stage.mobileConnected);
1322
1432
  case "capture":
1323
1433
  return renderCapture(stage.declinedReason);
1434
+ case "journey":
1435
+ return renderJourney(stage.url);
1324
1436
  case "submitting":
1325
1437
  return renderSubmitting();
1326
1438
  case "accepted":
@@ -1333,10 +1445,10 @@ function FaceCaptureModal({
1333
1445
  return renderError(stage.message);
1334
1446
  }
1335
1447
  })();
1336
- return /* @__PURE__ */ React__default.default.createElement(React__default.default.Fragment, null, /* @__PURE__ */ React__default.default.createElement("style", null, keyframes), /* @__PURE__ */ React__default.default.createElement("div", { style: overlayStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: modalStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: headerStyle }, /* @__PURE__ */ React__default.default.createElement(
1448
+ return /* @__PURE__ */ React__default.default.createElement(React__default.default.Fragment, null, /* @__PURE__ */ React__default.default.createElement("style", null, keyframes), /* @__PURE__ */ React__default.default.createElement("div", { style: overlayStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: { ...modalStyle, maxWidth: modalMaxWidth(journeyMode) } }, /* @__PURE__ */ React__default.default.createElement("div", { style: headerStyle }, /* @__PURE__ */ React__default.default.createElement(
1337
1449
  "button",
1338
1450
  {
1339
- onClick: () => close(null),
1451
+ onClick: handleHeaderClose,
1340
1452
  style: closeButtonStyle,
1341
1453
  "aria-label": "Close"
1342
1454
  },
@@ -1366,7 +1478,8 @@ var modalStyle = {
1366
1478
  borderRadius: 16,
1367
1479
  boxShadow: "0 25px 50px -12px rgba(0,0,0,.25)",
1368
1480
  width: "100%",
1369
- maxWidth: 448,
1481
+ maxHeight: "calc(100vh - 32px)",
1482
+ overflowY: "auto",
1370
1483
  animation: "zoomIn .2s ease-out"
1371
1484
  };
1372
1485
  var headerStyle = {
@@ -1392,6 +1505,13 @@ var contentStyle = {
1392
1505
  flexDirection: "column",
1393
1506
  gap: 16
1394
1507
  };
1508
+ var journeyFrameStyle = {
1509
+ width: "100%",
1510
+ height: "min(520px, 60vh)",
1511
+ border: 0,
1512
+ borderRadius: 12,
1513
+ background: "#f9fafb"
1514
+ };
1395
1515
  var visualGuideContainerStyle = { display: "flex", justifyContent: "center" };
1396
1516
  var visualGuideStyle = { position: "relative" };
1397
1517
  var circleStyle = {
@@ -1658,9 +1778,10 @@ function LiveCamera({ onCapture, onCancel, message, assetBasePath }) {
1658
1778
  if (error) {
1659
1779
  return /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center", padding: 0 } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Camera unavailable"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, error), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: secondaryButtonStyle, onClick: onCancel }, "Back")));
1660
1780
  }
1661
- const detectionActive = ready && !faceDetectionFailed;
1662
- const liveMessage = detectionActive ? FACE_MESSAGES[faceStatus] : message;
1663
- const liveFaceColor = detectionActive ? FACE_COLORS[faceStatus] : void 0;
1781
+ const detectionAvailable = !faceDetectionFailed;
1782
+ const detectionActive = ready && detectionAvailable;
1783
+ const liveMessage = detectionAvailable ? FACE_MESSAGES[faceStatus] : message;
1784
+ const liveFaceColor = detectionAvailable ? FACE_COLORS[faceStatus] : void 0;
1664
1785
  const captureDisabled = isCaptureDisabled(ready, detectionActive, faceStatus);
1665
1786
  const ringPulse = detectionActive && (faceStatus === "too_far" || faceStatus === "too_close") ? "pulse 1.5s ease-in-out infinite" : void 0;
1666
1787
  const dynamicCircleStyle = {
@@ -1776,20 +1897,22 @@ var faceOverlayContainerStyle = {
1776
1897
  };
1777
1898
  var faceRingWrapStyle = {
1778
1899
  position: "relative",
1779
- width: "72%",
1780
- aspectRatio: "1 / 1"
1900
+ height: "64%",
1901
+ aspectRatio: "3 / 4"
1781
1902
  };
1782
1903
  var faceDashedRingStyle = {
1783
1904
  position: "absolute",
1784
1905
  inset: -6,
1785
- borderRadius: "50%",
1786
- border: "6px dashed rgba(255, 255, 255, 0.4)",
1787
- animation: "spin 10s linear infinite"
1906
+ borderRadius: 9999,
1907
+ border: "6px dashed rgba(255, 255, 255, 0.4)"
1788
1908
  };
1789
1909
  var faceCircleStyle = {
1790
1910
  position: "absolute",
1791
1911
  inset: 0,
1792
- borderRadius: "50%",
1912
+ // Tailwind's `rounded-full` resolves to a very large fixed radius. On a
1913
+ // 3:4 box that creates the normal KYC capsule/head guide with straighter
1914
+ // sides; 50% would instead produce a mathematically perfect ellipse.
1915
+ borderRadius: 9999,
1793
1916
  border: "4px solid #00bc7d",
1794
1917
  boxShadow: "0 0 0 1000px rgba(0, 0, 0, 0.5)"
1795
1918
  };